[med-svn] [netepi-collection] 10/13: New upstream version 1.8.4

Andreas Tille tille at debian.org
Tue Dec 26 14:03:29 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository netepi-collection.

commit 65333442a5de058b78e3110ce2b1a0dcf3f05c4e
Author: Andreas Tille <tille at debian.org>
Date:   Tue Dec 26 14:57:16 2017 +0100

    New upstream version 1.8.4
---
 CHANGES                                            |  1302 ++
 CONTRIBUTORS                                       |    32 +
 LGPL.txt                                           |   504 +
 LICENCE                                            |   603 +
 LiveCD/README                                      |    23 +
 LiveCD/README.html                                 |   129 +
 LiveCD/config                                      |     8 +
 LiveCD/images/h5n1.jpg                             |   Bin 0 -> 446523 bytes
 LiveCD/images/netepi-live-splash.README            |    15 +
 LiveCD/images/netepi-live-splash.pcx               |   Bin 0 -> 17680 bytes
 LiveCD/images/netepi-live-splash.xcf               |   Bin 0 -> 36506 bytes
 LiveCD/mk-ubuntu-livecd                            |   502 +
 LiveCD/mk-ubuntu-livecd-7.10                       |   535 +
 MANIFEST.in                                        |    33 +
 Makefile                                           |    37 +
 PKG-INFO                                           |    10 +
 README                                             |  1002 +
 README.mac                                         |    78 +
 README.selinux                                     |    21 +
 Selenium/CollectionTest1.html                      |  1240 ++
 Selenium/CollectionTest2.html                      |   510 +
 Selenium/CollectionTest3a.html                     | 20059 +++++++++++++++++++
 Selenium/CollectionTest3b.html                     | 19575 ++++++++++++++++++
 Selenium/CollectionTest3c.html                     | 19333 ++++++++++++++++++
 Selenium/CollectionTest3d.html                     | 19333 ++++++++++++++++++
 Selenium/TestSuite.html                            |    43 +
 Selenium/make_CollectionTest3.py                   |   276 +
 Trac_licence.txt                                   |    32 +
 app/admin.js                                       |   470 +
 app/app.py                                         |    82 +
 app/backiframe.html                                |     8 +
 app/calendar.css                                   |   225 +
 app/calendar.js                                    |  1806 ++
 app/cmdline.py                                     |    94 +
 app/formhelpers.js                                 |   262 +
 app/help/admin_help.html                           |    55 +
 app/help/contributors.html                         |    52 +
 app/help/copyright.html                            |   622 +
 app/help/help.html                                 |    55 +
 app/help/index.html                                |    21 +
 app/help/wiki_help.html                            |   235 +
 app/helpers.js                                     |   824 +
 app/index.html                                     |    29 +
 app/lang/calendar-en.js                            |   127 +
 app/menu.py                                        |   161 +
 app/nobbleback.js                                  |    87 +
 app/printforms.css                                 |   192 +
 app/sorttable.js                                   |   119 +
 app/style-gallery.html                             |  1091 +
 app/style.css                                      |  1919 ++
 app/wiki.css                                       |    25 +
 casemgr/__init__.py                                |    18 +
 casemgr/addressstate.py                            |    71 +
 casemgr/admin/__init__.py                          |    18 +
 casemgr/admin/dropsyndrome.py                      |   104 +
 casemgr/admin/formedit.py                          |   385 +
 casemgr/admin/formmeta.py                          |   246 +
 casemgr/admin/formrollforward.py                   |    68 +
 casemgr/admin/questionedit.py                      |   540 +
 casemgr/admin/search.py                            |   290 +
 casemgr/admin/tablediff.py                         |   223 +
 casemgr/albasetup.py                               |   295 +
 casemgr/bulletins.py                               |    76 +
 casemgr/cached.py                                  |    62 +
 casemgr/caseaccess.py                              |    52 +
 casemgr/caseassignment.py                          |    51 +
 casemgr/casedupe.py                                |   118 +
 casemgr/casemerge.py                               |   414 +
 casemgr/cases.py                                   |   348 +
 casemgr/caseset.py                                 |   303 +
 casemgr/casestatus.py                              |    54 +
 casemgr/casetags.py                                |   233 +
 casemgr/cmdline/README                             |    35 +
 casemgr/cmdline/__init__.py                        |    18 +
 casemgr/cmdline/cmdcommon.py                       |   164 +
 casemgr/cmdline/dupscan.py                         |    61 +
 casemgr/cmdline/exportcases.py                     |   119 +
 casemgr/cmdline/exportform.py                      |    49 +
 casemgr/cmdline/exportreport.py                    |    43 +
 casemgr/cmdline/geocodechangedaddresses.py         |   170 +
 casemgr/cmdline/importxml.py                       |   234 +
 casemgr/cmdline/listforms.py                       |    35 +
 casemgr/cmdline/listreports.py                     |   116 +
 casemgr/cmdline/listsyndromes.py                   |    33 +
 casemgr/cmdline/notifychangedfields.py             |   249 +
 casemgr/cmdline/notifymon.py                       |    57 +
 casemgr/cmdline/personindex.py                     |    42 +
 casemgr/cmdline/report.py                          |   108 +
 casemgr/contacts.py                                |   324 +
 casemgr/credentials.py                             |   418 +
 casemgr/dataexport.py                              |   465 +
 casemgr/dataimp/__init__.py                        |    20 +
 casemgr/dataimp/common.py                          |    23 +
 casemgr/dataimp/dataimp.py                         |   461 +
 casemgr/dataimp/datasrc.py                         |   309 +
 casemgr/dataimp/editor.py                          |   651 +
 casemgr/dataimp/elements.py                        |   306 +
 casemgr/dataimp/xmlload.py                         |   119 +
 casemgr/dataimp/xmlsave.py                         |   141 +
 casemgr/demogfields.py                             |  1108 +
 casemgr/exportselect.py                            |    93 +
 casemgr/form_summary.py                            |   429 +
 casemgr/formmerge.py                               |   275 +
 casemgr/formutils/__init__.py                      |    27 +
 casemgr/formutils/delete.py                        |    43 +
 casemgr/formutils/deploy.py                        |    37 +
 casemgr/formutils/exclusiveop.py                   |    40 +
 casemgr/formutils/rename.py                        |    33 +
 casemgr/formutils/usedby.py                        |    48 +
 casemgr/fuzzyperson.py                             |    62 +
 casemgr/globals.py                                 |    35 +
 casemgr/handle_exception.py                        |   123 +
 casemgr/logview.py                                 |   116 +
 casemgr/mergelabels.py                             |    51 +
 casemgr/messages.py                                |    83 +
 casemgr/nickcache.py                               |    38 +
 casemgr/nicknames.py                               |   913 +
 casemgr/notification/__init__.py                   |    18 +
 casemgr/notification/client.py                     |   177 +
 casemgr/notification/daemon.py                     |   263 +
 casemgr/notification/socketcore.py                 |   109 +
 casemgr/notify.py                                  |    89 +
 casemgr/paged_search.py                            |   271 +
 casemgr/person.py                                  |   403 +
 casemgr/persondupe.py                              |   599 +
 casemgr/persondupecfg.py                           |   128 +
 casemgr/persondupestat.py                          |    50 +
 casemgr/personmerge.py                             |   335 +
 casemgr/phonetic_encode.py                         |  1297 ++
 casemgr/preferences.py                             |   145 +
 casemgr/printforms.py                              |   114 +
 casemgr/pwcrypt.py                                 |   116 +
 casemgr/reports/__init__.py                        |    30 +
 casemgr/reports/common.py                          |    37 +
 casemgr/reports/contactvis.py                      |   317 +
 casemgr/reports/crosstab.py                        |   390 +
 casemgr/reports/epicurve.py                        |   608 +
 casemgr/reports/export.py                          |   271 +
 casemgr/reports/linereport.py                      |   171 +
 casemgr/reports/report.py                          |   358 +
 casemgr/reports/reportcolumns.py                   |   426 +
 casemgr/reports/reportfilters.py                   |  1041 +
 casemgr/reports/store.py                           |   247 +
 casemgr/reports/xmlload.py                         |   317 +
 casemgr/resultpersons.py                           |   182 +
 casemgr/rights.py                                  |   240 +
 casemgr/schema/__init__.py                         |    18 +
 casemgr/schema/check.py                            |    92 +
 casemgr/schema/report_1_6/__init__.py              |    23 +
 casemgr/schema/report_1_6/convert.py               |    67 +
 casemgr/schema/report_1_6/report.py                |    97 +
 casemgr/schema/report_1_6/reportcolumns.py         |    72 +
 casemgr/schema/report_1_6/reportcrosstab.py        |    49 +
 casemgr/schema/report_1_6/reportfilters.py         |   250 +
 casemgr/schema/schema.py                           |   503 +
 casemgr/schema/seed.py                             |   145 +
 casemgr/schema/upgrade.py                          |   865 +
 casemgr/search.py                                  |   204 +
 casemgr/sendmail.py                                |    86 +
 casemgr/svnrev.py                                  |     1 +
 casemgr/syndcategorical.py                         |   208 +
 casemgr/syndrome.py                                |   346 +
 casemgr/tabs.py                                    |   105 +
 casemgr/taskdesc.py                                |   107 +
 casemgr/tasks.py                                   |   631 +
 casemgr/tasksearch.py                              |   299 +
 casemgr/unituser.py                                |   154 +
 casemgr/user_edit.py                               |   377 +
 casemgr/user_search.py                             |   201 +
 casemgr/version.py                                 |    22 +
 cocklebur/__init__.py                              |    18 +
 cocklebur/agelib.py                                |   292 +
 cocklebur/checkdigit.py                            |    51 +
 cocklebur/compat.py                                |    42 +
 cocklebur/countries.py                             |   280 +
 cocklebur/daemonize.py                             |    42 +
 cocklebur/datetime.py                              |   528 +
 cocklebur/dbobj/__init__.py                        |    40 +
 cocklebur/dbobj/cache.py                           |    46 +
 cocklebur/dbobj/column_describer.py                |   394 +
 cocklebur/dbobj/database_describer.py              |   474 +
 cocklebur/dbobj/dbapi.py                           |    45 +
 cocklebur/dbobj/exec_timing.py                     |    57 +
 cocklebur/dbobj/execute.py                         |    94 +
 cocklebur/dbobj/misc.py                            |   181 +
 cocklebur/dbobj/participation_table.py             |   242 +
 cocklebur/dbobj/query_builder.py                   |   393 +
 cocklebur/dbobj/result.py                          |   480 +
 cocklebur/dbobj/table_describer.py                 |   205 +
 cocklebur/dbobj/table_dict.py                      |    83 +
 cocklebur/dbobj/table_extras.py                    |    40 +
 cocklebur/exepath.py                               |    27 +
 cocklebur/filename_safe.py                         |    35 +
 cocklebur/foreign_key.py                           |    85 +
 cocklebur/form_ui/__init__.py                      |    24 +
 cocklebur/form_ui/columns.py                       |   134 +
 cocklebur/form_ui/common.py                        |    35 +
 cocklebur/form_ui/elements.py                      |   268 +
 cocklebur/form_ui/formdata.py                      |    36 +
 cocklebur/form_ui/formlib.py                       |   332 +
 cocklebur/form_ui/inputbase.py                     |   295 +
 cocklebur/form_ui/inputs/__init__.py               |    37 +
 cocklebur/form_ui/inputs/core.py                   |   139 +
 cocklebur/form_ui/inputs/datetime.py               |    86 +
 cocklebur/form_ui/inputs/health.py                 |    57 +
 cocklebur/form_ui/jsmeta.py                        |   149 +
 cocklebur/form_ui/pyload.py                        |    33 +
 cocklebur/form_ui/pysave.py                        |    98 +
 cocklebur/form_ui/xmlload.py                       |   231 +
 cocklebur/form_ui/xmlsave.py                       |   130 +
 cocklebur/group_edit.py                            |    80 +
 cocklebur/hsv.py                                   |    27 +
 cocklebur/introspect.py                            |    36 +
 cocklebur/languages.py                             |    60 +
 cocklebur/pageops.py                               |   325 +
 cocklebur/pt.py                                    |   175 +
 cocklebur/safehtml.py                              |   293 +
 cocklebur/tablesearch.py                           |    79 +
 cocklebur/template.py                              |    49 +
 cocklebur/temporary_file.py                        |    39 +
 cocklebur/tests/Makefile                           |     7 +
 cocklebur/tests/dbobj.py                           |   315 +
 cocklebur/trafficlight.py                          |    33 +
 cocklebur/tuplestruct.py                           |    67 +
 cocklebur/utils.py                                 |   104 +
 cocklebur/xmlparse.py                              |   261 +
 cocklebur/xmlwriter.py                             |   127 +
 config.py                                          |   164 +
 debian/README.Debian                               |     8 -
 debian/changelog                                   |     5 -
 debian/compat                                      |     1 -
 debian/control                                     |    57 -
 debian/copyright                                   |   548 -
 debian/rules                                       |    13 -
 debian/source/format                               |     1 -
 debian/watch                                       |     3 -
 doc/Collection-ER.dia                              | 11871 +++++++++++
 doc/Collection-ER.pdf                              |   Bin 0 -> 42374 bytes
 doc/Collection-ER.png                              |   Bin 0 -> 271239 bytes
 doc/CollectionApp.dot                              |   217 +
 doc/CollectionApp.svg                              |  2994 +++
 doc/DEVNOTES                                       |    25 +
 doc/HINTS                                          |    61 +
 doc/Makefile                                       |    37 +
 doc/sillyquery                                     |    58 +
 forms/hospital_admit.form                          |    58 +
 forms/lab_culture.form                             |    75 +
 forms/lab_direct_ag.form                           |    59 +
 forms/lab_haem_biochem.form                        |    92 +
 forms/lab_pcr.form                                 |   118 +
 forms/lab_serology.form                            |   148 +
 forms/sars_china.form                              |   154 +
 forms/sars_exposure.form                           |    37 +
 forms/sars_followup.form                           |   111 +
 forms/sars_hk.form                                 |    75 +
 forms/sars_onset.form                              |    48 +
 forms/sars_symptoms.form                           |   184 +
 forms/sars_travel.form                             |    57 +
 forms/spox_case.form                               |   220 +
 forms/spox_exposure.form                           |   126 +
 forms/spox_history.form                            |    90 +
 forms/spox_laboratory.form                         |    43 +
 forms/treatment_details.form                       |    68 +
 httpinteract/LICENCE                               |   541 +
 httpinteract/README                                |    54 +
 httpinteract/gnuplot.py                            |    44 +
 httpinteract/httpinteract.cgi                      |   132 +
 httpinteract/httpinteract.css                      |    86 +
 httpinteract/httpinteract.js                       |   270 +
 httpinteract/httpinteract_soomload.py              |   177 +
 httpinteract/index.html                            |    71 +
 httpinteract/schema                                |    20 +
 httpinteract/sitemap_tim.csv                       |    35 +
 images/add-input.png                               |   Bin 0 -> 1166 bytes
 images/add-paste.png                               |   Bin 0 -> 1333 bytes
 images/add-question.png                            |   Bin 0 -> 1465 bytes
 images/add-s.png                                   |   Bin 0 -> 599 bytes
 images/add-s.xcf                                   |   Bin 0 -> 1019 bytes
 images/add-section.png                             |   Bin 0 -> 1383 bytes
 images/add-subsec.png                              |   Bin 0 -> 1341 bytes
 images/add.xcf                                     |   Bin 0 -> 7970 bytes
 images/arrow-l.png                                 |   Bin 0 -> 348 bytes
 images/arrow-r.png                                 |   Bin 0 -> 339 bytes
 images/arrow.xcf                                   |   Bin 0 -> 1949 bytes
 images/box.png                                     |   Bin 0 -> 196 bytes
 images/button-add.png                              |   Bin 0 -> 1135 bytes
 images/button-del-nil.png                          |   Bin 0 -> 1126 bytes
 images/button-del.png                              |   Bin 0 -> 1239 bytes
 images/button-down.png                             |   Bin 0 -> 246 bytes
 images/button-edit-d.png                           |   Bin 0 -> 963 bytes
 images/button-edit-l.png                           |   Bin 0 -> 963 bytes
 images/button-edit.png                             |   Bin 0 -> 1261 bytes
 images/button-nil.png                              |   Bin 0 -> 168 bytes
 images/button-up.png                               |   Bin 0 -> 245 bytes
 images/close.png                                   |   Bin 0 -> 1628 bytes
 images/deletedbg.png                               |   Bin 0 -> 1357 bytes
 images/favicon.ico                                 |   Bin 0 -> 5006 bytes
 images/favicon.xcf                                 |   Bin 0 -> 3046 bytes
 images/help-g.png                                  |   Bin 0 -> 1963 bytes
 images/help-w.png                                  |   Bin 0 -> 2864 bytes
 images/help.xcf                                    |   Bin 0 -> 23976 bytes
 images/info.png                                    |   Bin 0 -> 1650 bytes
 images/netepi-1.xcf                                |   Bin 0 -> 31984 bytes
 images/netepi-2.xcf                                |   Bin 0 -> 24348 bytes
 images/netepi-3.xcf                                |   Bin 0 -> 139067 bytes
 images/netepi-bb.png                               |   Bin 0 -> 7310 bytes
 images/netepi.png                                  |   Bin 0 -> 4543 bytes
 images/plus-line.png                               |   Bin 0 -> 153 bytes
 images/plus.png                                    |   Bin 0 -> 596 bytes
 images/round-s.xcf                                 |   Bin 0 -> 5789 bytes
 images/round.xcf                                   |   Bin 0 -> 6811 bytes
 images/showbull.png                                |   Bin 0 -> 5016 bytes
 images/showbull.xcf                                |   Bin 0 -> 19130 bytes
 images/small-dot.xcf                               |   Bin 0 -> 19246 bytes
 images/sortarrow.xcf                               |   Bin 0 -> 9395 bytes
 images/sortdn.png                                  |   Bin 0 -> 559 bytes
 images/sortdnsel.png                               |   Bin 0 -> 635 bytes
 images/sortup.png                                  |   Bin 0 -> 520 bytes
 images/sortupsel.png                               |   Bin 0 -> 583 bytes
 images/title-gradient.png                          |   Bin 0 -> 397 bytes
 install.py                                         |   149 +
 labsurv/LICENCE                                    |   548 +
 labsurv/README                                     |    46 +
 labsurv/app/app.py                                 |   243 +
 labsurv/app/help.html                              |   133 +
 labsurv/app/helpers.js                             |   138 +
 labsurv/app/images/banner.jpg                      |   Bin 0 -> 2447 bytes
 labsurv/app/images/favicon.ico                     |   Bin 0 -> 766 bytes
 labsurv/app/images/help-g.png                      |   Bin 0 -> 2851 bytes
 labsurv/app/style.css                              |   185 +
 labsurv/install.py                                 |    96 +
 labsurv/labsurv/__init__.py                        |    16 +
 labsurv/labsurv/dbapi.py                           |   159 +
 labsurv/labsurv/labsurv.py                         |   686 +
 labsurv/labsurv/pcode.py                           | 14737 ++++++++++++++
 labsurv/liccheck.py                                |    77 +
 labsurv/pages/cases.html                           |    74 +
 labsurv/pages/date.html                            |    60 +
 labsurv/pages/details.html                         |    53 +
 labsurv/pages/macros.html                          |    69 +
 labsurv/pages/submit.html                          |   123 +
 labsurv/pages/totals.html                          |    42 +
 labsurv/schema/chown                               |     7 +
 labsurv/schema/schema.sql                          |    80 +
 labsurv/schema/upgrade1.sql                        |    10 +
 labsurv/simpleinst/__init__.py                     |    66 +
 labsurv/simpleinst/config_register.py              |   185 +
 labsurv/simpleinst/defaults.py                     |    25 +
 labsurv/simpleinst/filter.py                       |    33 +
 labsurv/simpleinst/glob.py                         |    49 +
 labsurv/simpleinst/install_files.py                |   190 +
 labsurv/simpleinst/platform.py                     |    65 +
 labsurv/simpleinst/pyinstaller.py                  |    30 +
 labsurv/simpleinst/usergroup.py                    |    47 +
 labsurv/simpleinst/utils.py                        |   112 +
 labsurv/tools/pcload.py                            |    38 +
 liccheck.py                                        |    93 +
 load/README                                        |   155 +
 load/formtest2.py                                  |   467 +
 load/testdata.csv.gz                               |   Bin 0 -> 42933 bytes
 mail/exception_notify                              |     4 +
 mail/register_invite                               |    11 +
 mail/registration_notify                           |    14 +
 mail/too_many_attempts                             |    12 +
 pages/__init__.py                                  |    18 +
 pages/admin.html                                   |   364 +
 pages/admin.py                                     |    88 +
 pages/admin_address_states.html                    |    67 +
 pages/admin_address_states.py                      |    60 +
 pages/admin_bulletin.html                          |    81 +
 pages/admin_bulletin.py                            |   102 +
 pages/admin_bulletins.html                         |    81 +
 pages/admin_bulletins.py                           |    60 +
 pages/admin_contact_type.html                      |    57 +
 pages/admin_contact_type.py                        |   101 +
 pages/admin_contact_types.html                     |    48 +
 pages/admin_contact_types.py                       |    52 +
 pages/admin_form_deploy.html                       |    77 +
 pages/admin_form_deploy.py                         |   112 +
 pages/admin_form_diff.html                         |    37 +
 pages/admin_form_diff.py                           |    53 +
 pages/admin_form_edit.html                         |   233 +
 pages/admin_form_edit.py                           |   243 +
 pages/admin_form_edit_input.html                   |   232 +
 pages/admin_form_edit_question.html                |   193 +
 pages/admin_form_edit_question.py                  |    90 +
 pages/admin_form_preview.html                      |    33 +
 pages/admin_form_preview.py                        |    46 +
 pages/admin_forms.html                             |    74 +
 pages/admin_forms.py                               |    58 +
 pages/admin_group.html                             |    85 +
 pages/admin_group.py                               |    87 +
 pages/admin_groups.html                            |    69 +
 pages/admin_groups.py                              |    53 +
 pages/admin_queue.html                             |   102 +
 pages/admin_queue.py                               |   113 +
 pages/admin_queues.html                            |    63 +
 pages/admin_queues.py                              |    53 +
 pages/admin_synd_categ.html                        |    76 +
 pages/admin_synd_categ.py                          |    74 +
 pages/admin_synd_clear.html                        |    63 +
 pages/admin_synd_clear.py                          |    53 +
 pages/admin_synd_drop.html                         |    63 +
 pages/admin_synd_drop.py                           |    53 +
 pages/admin_synd_fields.html                       |   143 +
 pages/admin_synd_fields.py                         |    83 +
 pages/admin_syndrome.html                          |   131 +
 pages/admin_syndrome.py                            |   169 +
 pages/admin_syndromes.html                         |    72 +
 pages/admin_syndromes.py                           |    65 +
 pages/admin_unit.html                              |   192 +
 pages/admin_unit.py                                |   122 +
 pages/admin_units.html                             |    95 +
 pages/admin_units.py                               |    87 +
 pages/admin_user.html                              |   193 +
 pages/admin_user.py                                |    94 +
 pages/admin_users.html                             |    71 +
 pages/admin_users.py                               |    73 +
 pages/admin_view_right.html                        |   127 +
 pages/admin_view_right.py                          |    46 +
 pages/admin_wikiedit.html                          |    56 +
 pages/admin_wikiedit.py                            |    64 +
 pages/bulletin_detail.html                         |    50 +
 pages/bulletin_detail.py                           |    44 +
 pages/bulletin_list.html                           |    47 +
 pages/case.html                                    |    59 +
 pages/case.py                                      |   234 +
 pages/caseaccess.html                              |    45 +
 pages/caseaccess.py                                |    67 +
 pages/casecontacts.html                            |   107 +
 pages/casecontacts.py                              |   107 +
 pages/casecontacts_assoc.html                      |   110 +
 pages/casecontacts_assoc.py                        |    69 +
 pages/caseform.html                                |    57 +
 pages/caseform.py                                  |   138 +
 pages/caseprint.html                               |   247 +
 pages/caseprint.py                                 |    31 +
 pages/caseset_ops.py                               |   159 +
 pages/casetask.html                                |    34 +
 pages/casetask.py                                  |    44 +
 pages/casetasks.py                                 |    68 +
 pages/dataimp.html                                 |   272 +
 pages/dataimp.py                                   |   201 +
 pages/dataimp_editfield.html                       |   274 +
 pages/dataimp_editfield.py                         |    63 +
 pages/dataimp_preview.html                         |    63 +
 pages/dataimp_preview.py                           |    75 +
 pages/dataimp_view.html                            |    39 +
 pages/dataimp_view.py                              |    26 +
 pages/demogfields.html                             |   161 +
 pages/dupecases.html                               |    62 +
 pages/dupecases.py                                 |    50 +
 pages/dupecases_config.html                        |    51 +
 pages/dupecases_config.py                          |    45 +
 pages/dupepersons.html                             |    96 +
 pages/dupepersons.py                               |   137 +
 pages/dupepersons_config.html                      |   133 +
 pages/dupepersons_config.py                        |    93 +
 pages/dupepersons_config_edit.html                 |    63 +
 pages/dupepersons_config_edit.py                   |    45 +
 pages/error_layout.html                            |    66 +
 pages/export.html                                  |   139 +
 pages/export.py                                    |    57 +
 pages/form.html                                    |   275 +
 pages/form_inputs.html                             |   169 +
 pages/group_edit.html                              |    42 +
 pages/login.html                                   |   131 +
 pages/login.py                                     |    74 +
 pages/logview.html                                 |    73 +
 pages/logview.py                                   |    34 +
 pages/main.html                                    |   153 +
 pages/main.py                                      |   226 +
 pages/manualdupe.html                              |    43 +
 pages/manualdupe.py                                |    56 +
 pages/mergecase.html                               |   119 +
 pages/mergecase.py                                 |    47 +
 pages/mergecase_detail.html                        |    54 +
 pages/mergecase_detail.py                          |    60 +
 pages/mergeform.html                               |   118 +
 pages/mergeform.py                                 |    50 +
 pages/mergeform_detail.html                        |    57 +
 pages/mergeform_detail.py                          |    53 +
 pages/mergeperson.html                             |   136 +
 pages/mergeperson.py                               |    67 +
 pages/mergeperson_detail.html                      |    54 +
 pages/mergeperson_detail.py                        |    65 +
 pages/nocookies.html                               |    31 +
 pages/notetask.html                                |    28 +
 pages/notetask.py                                  |    38 +
 pages/page_common.py                               |   367 +
 pages/page_layout.html                             |   420 +
 pages/prefsedit.html                               |   134 +
 pages/prefsedit.py                                 |    65 +
 pages/printform_inputs.html                        |   117 +
 pages/printforms.html                              |   133 +
 pages/printforms.py                                |    25 +
 pages/report_columns.html                          |   161 +
 pages/report_contactvis.html                       |    47 +
 pages/report_crosstab.html                         |    67 +
 pages/report_crosstab.py                           |    40 +
 pages/report_edit.html                             |   235 +
 pages/report_edit.py                               |   217 +
 pages/report_epicurve.html                         |    86 +
 pages/report_filters.html                          |   191 +
 pages/report_image.html                            |    35 +
 pages/report_image.py                              |    47 +
 pages/report_menu.html                             |    79 +
 pages/report_menu.py                               |    75 +
 pages/report_ops.py                                |    60 +
 pages/report_orderby.html                          |    46 +
 pages/report_table.html                            |    82 +
 pages/report_table.py                              |    60 +
 pages/result.html                                  |   108 +
 pages/result.py                                    |    49 +
 pages/search.html                                  |   154 +
 pages/search.py                                    |    67 +
 pages/search_ops.py                                |   269 +
 pages/search_pt.html                               |   114 +
 pages/selmergecase.html                            |    98 +
 pages/selmergecase.py                              |    47 +
 pages/selmergeforms.html                           |    99 +
 pages/selmergeforms.py                             |    52 +
 pages/selprintforms.html                           |    68 +
 pages/selprintforms.py                             |    51 +
 pages/shutdown.html                                |    29 +
 pages/syn_detail.html                              |    33 +
 pages/syn_detail.py                                |    38 +
 pages/syndlist.html                                |    92 +
 pages/tabs.html                                    |    59 +
 pages/tagbrowse.html                               |    48 +
 pages/tagbrowse.py                                 |    56 +
 pages/tagedit.html                                 |    54 +
 pages/tagedit.py                                   |    68 +
 pages/taskaction.py                                |    79 +
 pages/taskbanner.html                              |    35 +
 pages/taskedit.html                                |   259 +
 pages/taskedit.py                                  |   170 +
 pages/tasks.html                                   |   144 +
 pages/tasks.py                                     |    66 +
 pages/tools.html                                   |   103 +
 pages/tools.py                                     |    87 +
 pages/traceback.html                               |    32 +
 pages/unitselect.py                                |    51 +
 pages/unitview.html                                |    49 +
 pages/unitview.py                                  |    47 +
 pages/user_browser.html                            |    25 +
 pages/user_browser.py                              |    48 +
 pages/user_queue.html                              |    99 +
 pages/user_queue.py                                |    28 +
 pages/user_queues.html                             |    63 +
 pages/user_queues.py                               |    53 +
 pages/user_search.html                             |    68 +
 pages/user_sponsor.html                            |   101 +
 pages/user_sponsor.py                              |   114 +
 pages/useradmin.html                               |    72 +
 pages/useradmin.py                                 |    73 +
 pages/useredit.html                                |   252 +
 pages/useredit.py                                  |    68 +
 sdist.py                                           |    42 +
 simpleinst/__init__.py                             |    66 +
 simpleinst/config_register.py                      |   185 +
 simpleinst/defaults.py                             |    25 +
 simpleinst/filter.py                               |    33 +
 simpleinst/glob.py                                 |    49 +
 simpleinst/install_files.py                        |   190 +
 simpleinst/platform.py                             |    65 +
 simpleinst/pyinstaller.py                          |    30 +
 simpleinst/usergroup.py                            |    47 +
 simpleinst/utils.py                                |   112 +
 test.py                                            |    67 +
 tests/__init__.py                                  |    18 +
 tests/adminformedit.py                             |    94 +
 tests/age.py                                       |    85 +
 tests/data/adminformedit/test0.form                |    35 +
 tests/data/adminformedit/test1.form                |    38 +
 tests/data/adminformedit/test2.form                |    38 +
 tests/data/adminformedit/test3.form                |    38 +
 tests/data/adminformedit/test4.form                |    38 +
 tests/data/adminformedit/test5.form                |    59 +
 tests/data/export/doharesult.csv                   |     4 +
 tests/data/export/formresult.csv                   |     4 +
 tests/data/export/result.csv                       |     4 +
 tests/data/hospital_admit.form                     |    58 +
 tests/data/sars_exposure.form                      |    48 +
 tests/data/tables/address_states.csv               |     9 +
 tests/data/tables/case_acl.csv                     |     6 +
 tests/data/tables/case_form_summary.csv            |     5 +
 tests/data/tables/case_tags.csv                    |     4 +
 tests/data/tables/cases.csv                        |     6 +
 tests/data/tables/forms.csv                        |     3 +
 tests/data/tables/hospital_admit.csv               |     1 +
 tests/data/tables/persons.csv                      |     4 +
 tests/data/tables/sars_exposure.csv                |     5 +
 tests/data/tables/syndrome_case_assignments.csv    |    19 +
 tests/data/tables/syndrome_case_status.csv         |     5 +
 tests/data/tables/syndrome_forms.csv               |     4 +
 tests/data/tables/syndrome_types.csv               |     3 +
 tests/data/tables/tags.csv                         |     4 +
 tests/data/tables/units.csv                        |     4 +
 tests/data/tables/users.csv                        |     3 +
 tests/dataimp/__init__.py                          |    18 +
 tests/dataimp/dataimp.py                           |   307 +
 tests/dataimp/datasrc.py                           |   113 +
 tests/dataimp/editor.py                            |   253 +
 tests/dataimp/xmlsaveload.py                       |    79 +
 tests/datetimetest.py                              |   262 +
 tests/dbobj/__init__.py                            |    37 +
 tests/dbobj/participation_table.py                 |   243 +
 tests/dbobj/query_builder.py                       |   392 +
 tests/dbobj/result.py                              |   353 +
 tests/demogfields.py                               |   209 +
 tests/export.py                                    |   117 +
 tests/form.py                                      |    83 +
 tests/formcommon.py                                |    40 +
 tests/formlib.py                                   |   153 +
 tests/formsave.py                                  |   125 +
 tests/parse_addr.py                                |    71 +
 tests/person.py                                    |    47 +
 tests/persondupe.py                                |   140 +
 tests/reports/__init__.py                          |    18 +
 tests/reports/columns.py                           |   134 +
 tests/reports/common.py                            |    75 +
 tests/reports/crosstab.py                          |   170 +
 tests/reports/export.py                            |    70 +
 tests/reports/filters.py                           |   465 +
 tests/reports/linereport.py                        |    66 +
 tests/reports/orderby.py                           |    69 +
 tests/reports/savenload.py                         |   210 +
 tests/searchacl.py                                 |   117 +
 tests/testcommon.py                                |   238 +
 tests/tuplestruct.py                               |    51 +
 tests/wikiformatting.py                            |   228 +
 tests/xmlparse.py                                  |   123 +
 tools/client_report.py                             |   431 +
 tools/client_report1.png                           |   Bin 0 -> 46623 bytes
 tools/client_report2.png                           |   Bin 0 -> 36801 bytes
 tools/client_report3.png                           |   Bin 0 -> 50539 bytes
 tools/client_report4.png                           |   Bin 0 -> 58924 bytes
 tools/client_report5.png                           |   Bin 0 -> 46934 bytes
 tools/client_report6.png                           |   Bin 0 -> 36548 bytes
 tools/client_report_page.py                        |    64 +
 tools/compile_db.py                                |   139 +
 tools/csvfilter.py                                 |   149 +
 tools/csvsplit.py                                  |    98 +
 tools/desc_csv.py                                  |    89 +
 tools/dupescangraph.py                             |    43 +
 tools/extract_demogfields.py                       |    48 +
 tools/faxtag.py                                    |   121 +
 tools/form_to_xml.py                               |    37 +
 tools/formexport.py                                |   204 +
 tools/hdfilter.py                                  |   206 +
 tools/hrigcalc.html                                |   180 +
 tools/mod_auth_pgsql-2.0.3-netepi.4101/INSTALL     |    48 +
 tools/mod_auth_pgsql-2.0.3-netepi.4101/Makefile    |    12 +
 tools/mod_auth_pgsql-2.0.3-netepi.4101/README      |    22 +
 .../mod_auth_pgsql-2.0.3-netepi.4101/README.netepi |    62 +
 tools/mod_auth_pgsql-2.0.3-netepi.4101/TODO        |     7 +
 .../mod_auth_pgsql.c                               |  1236 ++
 .../mod_auth_pgsql.html                            |   608 +
 tools/pgcasttest.py                                |    59 +
 tools/populate_cases_contacts.py                   |   114 +
 tools/populate_cases_random.py                     |    78 +
 tools/populate_tasks.py                            |   108 +
 tools/quarantine_export.py                         |   183 +
 tools/seed/data/case_status                        |     4 +
 tools/seed/data/forms                              |     0
 tools/seed/data/group_syndromes                    |     4 +
 tools/seed/data/groups                             |     6 +
 tools/seed/data/syndrome_forms                     |    16 +
 tools/seed/data/syndrome_types                     |     2 +
 tools/seed/data/unit_groups                        |     9 +
 tools/seed/data/unit_users                         |     8 +
 tools/seed/data/units                              |     6 +
 tools/seed/data/users                              |     8 +
 tools/seed/seed_db                                 |    48 +
 tools/userdump                                     |    25 +
 tools/userrestore                                  |    43 +
 tools/visdb.py                                     |    47 +
 tools/xmltagfix.py                                 |    36 +
 wiki/__init__.py                                   |     0
 wiki/api.py                                        |   217 +
 wiki/core.py                                       |   209 +
 wiki/env.py                                        |   137 +
 wiki/formatter.py                                  |   699 +
 wiki/href.py                                       |   163 +
 wiki/log.py                                        |    70 +
 wiki/util.py                                       |   635 +
 687 files changed, 197772 insertions(+), 636 deletions(-)

diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..cc7ac71
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,1302 @@
+CUMULATIVE CHANGES IN NetEpi Collection
+=======================================
+
+SUMMARY OF CHANGES IN VERSION 1.8.4 RELATIVE TO VERSION 1.7.2
+=============================================================
+
+This round of changes was funded by the Australian Government Department of
+Health and Ageing.
+
+* Added support for importing data to checkbox fields. When using the
+  "Multivalue" column import, the data must appear in a single field with
+  a delimiter character ("/" by default). Exports from NetEpi Collection
+  already use this format.
+
+* The admin form editor has been reimplemented, now relying heavily on
+  Javascript for it's operation. In particular, a more powerful "cut
+  and paste" mechanism for moving and duplicating inputs, questions and
+  sections replaces the "move up", "move down", and "delete" buttons.
+  Cut&paste between forms is also possible, as the cut buffer is retained
+  between invocations of the form editor.
+
+* The admin interface for editing address "states" and "assignments",
+  and the form editor checkbox and droplist choices now use client-side
+  scripting allow reordering of the options without requiring a round-trip
+  to the server (as the former "up" and "down" buttons did).
+
+
+SUMMARY OF CHANGES IN VERSION 1.7.2 RELATIVE TO VERSION 1.6.0
+=============================================================
+
+This round of changes was funded by the Australian Government Department of
+Health and Ageing. The changes focused on improvements to reporting system:
+
+* A new "reports" menu has been introduced, making it easier to browse
+  saved reports.
+
+* Report filtering now supports logical conjunctions and disjunctions (AND and
+  OR).
+
+* There are now four levels of report sharing: "quick" (shown on home page),
+  "public" (available to all users), "role" (available to everyone in your
+  role) and "private" (available only to you).
+
+* A new "PUBREP" right has been introduced that allows users to run reports
+  that have been shared with them, without giving them full access to the
+  reporting system.
+
+* The graphical reports, "Epi curve" and "Association Visualisation" are
+  now report types in their own right, rather than being a sub-function of
+  line reports.
+
+* Reports are now saved in XML format (rather than as Python pickles), and
+  reports can now be imported and exported, facilitating sharing of reports
+  and improving the robustness of saved reports.
+
+* Line report output can now be generated in CSV format (similar to the
+  record export function, but allows filtering and control over the columns
+  exported).
+
+* Cross-tab reports now accept a third "page" axis, as well as options to
+  suppress empty rows, columns and pages.
+
+* Epi Curve (time series) reports now accept categorical form fields as
+  conditioning columns, as well as the core demographic fields, and the
+  generation of tick-marks on the time axis has been improved considerably.
+
+* Reports can now be run from the command-line utility.
+
+In addition to the reporting enhancements, the "date-of-birth/age" demographic
+field logic has been reimplemented to record the precision of the entered age,
+improving the handling of ages in days, weeks and months.
+
+These changes required changes to the database schema. The installer performs
+the necessary upgrades. As a result, it is not possible to downgrade from
+1.7 to 1.6.
+
+
+SUMMARY OF CHANGES IN VERSION 1.6.0 RELATIVE TO VERSION 1.5.2
+=============================================================
+* The "home" page has been redesigned to reduce clutter while providing
+  direct access to things like "recent cases" and the "quick search"
+  function. 
+  
+  NOTE: As this required extensive use of Javascript, browsers without
+  Javascript are no longer supported.
+
+* Minor improvements to usability of the case and person merging have been
+  made. 
+
+SUMMARY OF CHANGES IN VERSION 1.5.2 RELATIVE TO VERSION 1.5.1
+=============================================================
+
+* Add support to crosstabs for filtering by a form that is not a row or column.
+
+* Fixed inconsistent handling of form fields with a maximum length of 0 (this
+  now means "unlimited").
+
+* Changed wiki links to always pop in a new window (otherwise we lose
+  the application state).
+
+SUMMARY OF CHANGES IN VERSION 1.5.1 RELATIVE TO VERSION 1.5.0
+=============================================================
+
+* The demographic field "tabs" on the case and search screens have been
+  replaced by client-side dynamic HTML "folds".
+
+* The data import machinery has been reworked to improve its handling and
+  reporting of import errors, and test coverage of the import logic has been
+  improved considerably.
+
+* The unit test driver has been reimplemented to simplify the setup of test
+  cases and suites, and to allow a single scratch database instance to be
+  shared between all test cases. Previously, the scratch database was created
+  and destroyed for each test case, which was slow and expensive.
+
+* It is now possible to list and export form definitions via the command line
+  interface.
+
+* Made a number of improvements to the wording of messages within the
+  application, and made other cosmetic changes, such as highlighting the field
+  labels in the search result screen.
+
+* A number of regressions that were preventing use with Python 2.3 were fixed,
+  as were a number of other minor bugs. Also, when adding a new case, the
+  results were incorrectly being restricted to the current case definition.
+
+SUMMARY OF CHANGES IN VERSION 1.5.0 RELATIVE TO VERSION 1.4.6
+=============================================================
+
+* An extensive reworking of the contacts-to-case association mechanism has been
+  undertaken. Up until v1.5.0, an association between a contact of a case and a
+  case could only be recorded through the case record, and each contact record
+  could only be associated with a single case (although each person could have
+  multiple contact records, each associated with a different case). All of this
+  was overly restrictive and potentially rather confusing, and many end users
+  inadvertently entered contacts-of-cases around the wrong way. To address
+  these problems, NetEpi now allows arbitrary, bi-directional associations
+  between cases to be captured. In addition, such associations can now be
+  captured between cases of any case definition (syndrome) type to which the
+  user has access - contact associations are no longer restricted purely to
+  pre-defined and specific "contact" definitions.  Furthermore, a type of
+  association (or a type of relationship) can now be specified when recording
+  associations between cases, with the type chosen from a pre-defined list. New
+  association types can be added to this list on-the-fly by end users. An
+  association date can also be recorded. Also, associations can be added for
+  many records at a time, in a single operation, rather than tediously for
+  individual records one-by-one. This is a particularly useful feature when
+  used in conjunction with the new record tagging and case sets facilities
+  described below.
+
+* A case tagging facility has been added. During the public health response to
+  influenza A H1N1 09 (human swine flu) in NSW, Australia in April-August 2009,
+  we relabelled the "Facsimile (fax) number" field in the demographic fields
+  section of NetEpi as "Tags" and used it to record short codes (such as 'PD1')
+  to tag (or flag) records as belonging to a particular group, such as
+  passengers on a particular cruise ship who were potentially exposed to
+  influenza A H1N1 09 virus. This was found to be a very useful feature, but
+  the use of purely free text tags was somewhat problematic. The tagging
+  facility introduced in v1.5 corrects these problems: short tags can be
+  quickly entered as free text in a new "tags" field, which is associated with
+  each case record, not the person record to which the case relates. However,
+  these free text tags are validated against a list of pre-defined tags to
+  avoid typographic errors. Alternatively, tags may be chosen from a list. End
+  users may add new tags, together with a short description or scope note for
+  each tag, as they are required - intervention by an administrator is not
+  required to define new tags. The tags field can thus be used to record
+  membership of arbitrary groups, such as cases with exposure to putative or
+  potential sources of infection or environmental contaminant. Each case record
+  can have more than one tag applied to it. The label for the Tags field can be
+  over-ridden for each case definition eg it can be relabelled "Exposures".
+  Records with particular tags can be located using the search facilities, and
+  can easily be converted into case sets (see below). Tags can also be used in
+  filters for line reports and cross-tabs (and forthcoming in v1.6, to filter
+  data exports as well).
+
+* A "case set" facility has been added. This allows arbitrary sets of records
+  to be browsed or edited as a set of records. Case sets can be temporary, 
+  lasting only for the duration of a login session, or they can be given
+  names and saved for future reference. Case sets can be created by selecting 
+  some or all of the records returned by a search made through the search page.
+  Case sets can also be used to add associations (contacts) to a case, or can
+  be formed from all the associations for a case. Saved case sets can be
+  renamed, deleted or sorted in different ways. In v1.5.0, individual records
+  can be added to or removed from a case set once it has been created. Future
+  versions shall also allow case sets to be merged and to be used with the
+  tasking subsystem, and shall provide other ways of creating case sets, such
+  as automatically updated case sets of all records which do not meet a 
+  user-defined set of quality assurance or completeness criteria.
+
+* A new DATAMGR (data manager) right has been added, which allows users who
+  have been assigned this right to carry out duplicate person and duplicate
+  case scans and to merge duplicate person and/or case records (and data
+  forms). Prior to v1.5.0, only users with full administrator rights could
+  undertake scans for and merging of duplicate records. This was found to be
+  inconvenient in practice, particularly during an emergency, when
+  administrators tended to be rather busy with other tasks.
+
+* Further enhancements have been made to the person duplicate detection sub-
+  system. There are two options: a full duplicate scan and a quick scan. The 
+  full scan now compares every person record with every other person record, 
+  but due to optimisations this exhaustive comparison is faster than previous
+  partial (indexed) comparisons. The quick scan compares records that have been
+  edited since the last duplicate scan with all other records - in other words,
+  it is an incremental scan. Both the full and quick scans run in the back-
+  ground, and changes have been made to prevent manual person merging while 
+  background duplicate scans are running, and to prevent more than one 
+  duplicate scan from running at once.
+
+* A batch duplicate scan script has also been provided in the casemgr/cmdline
+  directory of the distribution. This can be run as an automated "cron" job
+  to scan for person duplicates on a regular basis eg overnight, or every hour.
+  The results of the scan are stored and are then immediately available to 
+  users who have the necessary rights [ADMIN or DATAMGR] to undertake person
+  merging.
+
+* A facility to scan for duplicate cases has been added (under the Tools menu,
+  users who have the necessary rights [ADMIN or DATAMGR]). The scan is
+  performed interactively (it runs quickly) for specific case definitions
+  (syndromes), rather than all case definitions, because not all case
+  definitions are singleton i.e. it may be acceptable, or expected, that a
+  person will have more than one case of a particular type. Thus the facility
+  should only be used to scan for duplicate cases where each person is expected
+  to have only one case. The intention is to enhance this facility in future
+  versions to allow detection of duplicate cases within a given temporal
+  window.
+
+* When cases are merged, the redundant case is now logically deleted, and a
+  note is automatically added to it which points to the case ID into which it
+  has been merged. Furthermore, when a search for a case ID is made using the
+  "quick search" field on the search page, the case IDs of deleted cases are
+  also searched. This helps prevent dismay amongst users who search for a case
+  ID which they knew existed but can no longer be found, because it has been
+  merged with another case.
+
+* A new user invitation and sponsorship system has been introduced. Previously,
+  new users wishing to apply for a user account could access the login page and
+  click on the "Apply for a user account" button without any form of
+  authentication.  This scheme was potentially vulnerable to imposters applying
+  for accounts, and thus required very careful and potentially very
+  time-consuming validation by administrators of the bona fides of account
+  applicants. This scheme is still the default, but additional account
+  application schemes are now available, which can be specified at installation
+  time and/or later by a system administrator. A new "invite" mode allows users
+  who have been given the new SPONSOR ("Sponsor new users") right to invite new
+  users to register for a user account by entering their email address. A
+  message which contains a random, one-time-use passcode is then sent to the
+  invited person's email address.  The invitee is prompted to provide this
+  one-time passcode before they are allowed to proceed with applying for an
+  account. After they have completed their account application, their details
+  must still be validated and approved by an administrator. The other new mode,
+  "sponsor", works in a similar fashion but both the ability to issue
+  invitations to apply for new user accounts and the ability to approve the
+  account applications for which they have issued invitations is devolved to
+  users who have been given the SPONSOR right. This mode completely avoids the
+  reliance on a small number of administrators to approval all new accounts,
+  while simultaneously dramatically reducing the likelihood of false account
+  applications by imposters. All account invitations and sponsorships are
+  logged in the central audit trail facility, and administrators or other
+  nominated users can be informed by email of any new invited or sponsored
+  account applications or approvals.
+
+* Improvements were made to the way in which passwords were encrypted, and the
+  requirements for password complexity were strengthened. Passwords must now be
+  a minimum of 6 characters long and be a mixture of upper and lower case
+  letters and numbers, and upper case letters and numbers must appear in the
+  body of the password, not just as the first and/or last characters.
+
+* A customised Apache web server authentication module, based on
+  mod_auth_pgsql, is included in the tools/ subdirectory. When installed and
+  configured, this module allows authenticated access to web content managed
+  outside of the NetEpi application using the same usernames and passwords that
+  are used to log in to NetEpi itself. This is useful for providing
+  password-protected access to reports or other content, such as maps,
+  generated by an external process based on data automatically exported from
+  NetEpi, without having to manage or issue additional passwords to users.
+
+* A "case assignment" field was added to the core demographic fields to capture
+  which organisational unit (eg which Public Health Unit, or which juris-
+  dictional unit) each case has been assigned to (if any). This often differs
+  from the geographical region of residence or employment of each case. The
+  valid values for this field can be specified by administrators for each case
+  definition/ syndrome.
+
+* A "recently accessed cases" pull-down list was added to the home page. This
+  list provides direct access, without having to go through a search page, to
+  the 16 cases which the user has most recently accessed or edited or added. It
+  is somewhat akin to the "history" function in most web browsers.
+
+* Improvements were made to the ergonomics of searching via the search page:
+  pressing Enter after filling in the Quick Search field now executes the
+  search immediately; the use of a wildcard character (*) in a search field now
+  automatically disables phonetic searching; and if a search returns only one
+  result, the user is shown that case record directly rather than an inter-
+  mediate search results page with just one row on it. This last feature is
+  optional, controlled by a configuration option which system administrators
+  can set.
+
+* It is now possible to embed the following strings in report and cross-tab 
+  headers, pre-ambles and footers and have the corresponding information
+  substituted when the report or cross-tab is run: {{date}}, {{datetime}},
+  {{username}}, {{fullname}}, {{syndrome_name}}, {{syndrome_label}}.
+
+* Data collection forms deletions are now logical, rather than absolute, and
+  thus deleted forms can still be viewed (but not edited), and can be undeleted
+  if required.
+
+* Several minor bugs which manifest themselves in obscure circumstances have
+  been fixed.
+
+SUMMARY OF CHANGES IN VERSION 1.4.6 RELATIVE TO VERSION 1.4.5
+======================================================================
+
+* Significant enhancements to the person duplicate detection facility and the
+  associated interactive person/case/form merging machinery. In particular,
+  an "only changed persons" optimisation for the duplicate person scanning
+  was added which makes incremental scanning much, much faster. In addition,
+  if an excessive number of matches occur while duplicate scanning, the
+  match cut-off is temporarily increased (otherwise up to persons^2 matches
+  can be recorded!). The minimum cut-off was also set at 55%.
+
+* Changes to search page appearance to more clearly distinguish the
+  "general" (lookup) search page from the search page used when adding
+  a new case or contact.
+
+* Added a record count to the footer of printed line reports.
+
+* Added the ability to capture a short textual reason for the deletion of
+  case and contact records.
+
+* Added a new class of message to users: "warning" (orange), in addition
+  to existing "info" (green) and "error" (red) message classes.
+
+* In the admin module, if searches for users, roles, forms etc return
+  only one result, jump straight to the relevant edit page.
+
+* Addition of a command-line tool to extract address details for all
+  new and changed records and print them to stdout or a CSV file, from
+  where they can be submitted to a geocoder for incremental geocoding
+  (see the cmdline/ directory).
+
+* Addition of a simple script to generate a description of the columns in
+  an exported CSV file - this is particularly useful for providing some
+  machine-readable metadata for data exported via the command-line/cron
+  job batch export facility. See the tools/ directory.
+
+* Addition of more last update timestamps on various entities.
+
+* Minor bug fixes, minor cosmetic and/or semantic adjustments to labels and 
+  messages, and some minor performance improvements.
+
+
+SUMMARY OF CHANGES IN VERSION 1.4.5 RELATIVE TO VERSION 1.4.3
+======================================================================
+
+* A cross-tabulation report type has been implemented. Counts of cases, 
+  contacts and forms can be readily generated. Clicking on a cell in the
+  resulting cross-tab generates a query for cases or contacts matching the row
+  and column criteria, and the resulting record set can then be stepped through
+  in the case or contact edit pages.
+
+* An additional check has been added when a user saves a singleton form to
+  ensure another user has not already created an instance of the singleton
+  form. If this has occurred, the forms are merged and the second user is
+  warned to review the data before re-saving it.
+
+* A rights-based user viewer has been added to the Admin interface, allowing
+  administrators to rapidly determine which users have a given Right
+  (permission), and how they acquired the right (via Context, Role or
+  directly).
+
+* In the form editor, you can now show the differences between the current 
+  form definition and the deployed form definition (this functionality requires
+  python 2.4 or greater. When an attempt is made to use it with python 2.3 a
+  harmless exception will occur).
+
+* A bug that resulted in spurious and transient validation errors occasionally 
+  being displayed when first opening a form has been fixed. The problem was
+  only evident when persistent application servers were in use (for example,
+  FastCGI), and did not cause any problems (it has been present for several
+  years and no-one has reported it), but it did cause occasional
+  head-scratching when strange validation errors would sometimes appear when
+  opening a new, blank data collection form.
+
+* The schema upgrade logic used an incorrect default when creating the 
+  DOB_is_approx column. This only effected databases that existed prior to May
+  2006. The consequences of this error were that the date of birth would be
+  treated as approximate even when an exact date of birth was supplied. For
+  effected systems, the default for DOB_is_approx was incorrectly specified as
+  TRUE. It will be necessary to manually fix effected schemas via psql and the
+  following command:
+
+    ALTER TABLE persons ALTER COLUMN DOB_is_approx SET DEFAULT FALSE
+
+* When deployed with the "ocpgdb" DB-API module (rather than pyPgSQL), a bug in
+  PostgreSQL was exposed that allowed invalid times to be saved to the database
+  when using the "Time" form input. This subsequently caused failures when
+  attempting to restore database dumps, as well as other problems with the
+  data.  Additional checks have been added to the application and the bug has
+  been reported to the PostgreSQL developers.
+
+* The handling of report filters where the filter was filtering on
+  form date/times has been fixed.
+
+* The report system's handling of changes to form definitions has been improved:
+  after form upgrades, (green) advisory warnings to check the validity of
+  report parameters are still given until report specifications are re-saved,
+  but reports now continue to function. Previously reports refused to run after
+  form upgrades until their parameters were checked by loading them and
+  re-saving them. Although this is good practice, the requirement was a bit too
+  strict.
+
+* Several changes have been made to the CSV data export functions to assist in
+  scripted and interactive (via the web interface) exporting. These include:
+
+   * A new row-per-form export style has been added to complement the existing 
+     row-per-case export styles. This is more natural for certain types of
+     analysis, and helps avoid problems when exporting into Excel (which does
+     not cope with spreadsheets with more than 256 columns).
+
+   * A flag has been added to optionally make the exporter remove newlines and
+     other control characters embedded within CSV fields.
+    
+   * Command line exports now use the "write to temporary file and rename into
+     place" pattern to avoid potential race conditions when other systems read
+     an export file that is currently being updated.
+  
+   * A bug in the command line exporter's handling of the "export deleted 
+     cases" flags was fixed.
+
+* A logic error in task-based contact access control has been fixed.
+
+* Two issues relating to the handling of RadioButton and DropList form inputs 
+  with null keys have been fixed (these mainly effected the Countries and
+  Languages input classes).
+
+* An issue when importing an XML form definition into the form editor has been
+  fixed. After an import, the form definition was not being marked as having
+  been changed and consequently the "Save" button was not available.
+
+* An issue that resulted in clients who were using systems such as Microsoft 
+  SharePoint producing harmless but annoying exceptions on the server each
+  time they logged in has been fixed.
+
+
+SUMMARY OF CHANGES IN VERSION 1.4.3 RELATIVE TO VERSION 1.4.0
+======================================================================
+
+* A regression in handling of RadioButton and DropList form inputs has been
+  fixed.
+
+* A race condition when rolling out changes to a form definition has been
+  fixed: if a user started filling in a form but had not saved it, the form
+  would be saved with the old form definition. The application now checks the
+  form definition version when saving the user's form and attempts to upgrade
+  it to the newly deployed version (and warns the user that this has occurred).
+
+* Fixed date/time handling in data import. For date/time fields, the date and
+  time specification was ambiguous as MM was used to represent both months and
+  minutes. The time format must now be specified in lower case (for example,
+  hh:mm:ss). Additionally, a logic error that resulted in an application
+  exception when previewing date/time fields has been fixed.
+
+* The third-party library responsible for translating wiki-style markup was
+  initialising the logging system on each call. When the application was
+  deployed as a persistent application server (via FastCGI or similar), the
+  application would slow down noticeably over a period of hours due to this
+  misuse of the logging module. The logging logic has been removed from the
+  wiki markup library.
+
+
+SUMMARY OF CHANGES IN VERSION 1.4.0 RELATIVE TO VERSION 1.3.0
+======================================================================
+
+User database
+-------------
+
+* Groups and Units have been generalised into Contexts and Roles. Contexts
+  allow a single instance to present different "faces" to different users
+  concurrently, while still sharing a single user database. 
+  
+* When a user is a member of multiple Roles, they are able to switch Roles 
+  via a pull-down menu on the main application page, and this choice is
+  remembered between logins.
+
+* Rights can now be associated with Roles as well as Users and Contexts
+  (formerly Groups). A user's rights are the union of their Role rights, their
+  Context rights, and their explicit user rights.  
+
+* User data has been expanded to allow the user database to be used as a
+  contact database. A simple user browser has been added under Tools, and users
+  can control the visibility of their details in the browser via their privacy
+  preferences. At login, users are periodically prompted to review their
+  contact details for correctness. The periodical reminders are controlled by
+  the user_check_interval configuration option.
+
+  The user browser can be disabled via the user_browser configuration option
+  (see the Installation section in the README file for more information).
+
+* A new access model that allows users to see all cases and contacts associated
+  with the case definitions assigned to their Role (rather than their Roles
+  cases and contacts) has been added.  This access is controlled by the "Access
+  by case/contact definition" right, which can be assigned to Users, Roles or
+  Contexts.
+
+* Disabled users (or users waiting for their account to be enabled) can now
+  update their contact details.
+
+* Administrators can now view the system log via the web interface. The system
+  log contains information about administrative changes, such as changes to
+  users, roles, contexts, case definitions, etc.
+
+Data import
+-----------
+
+* It is now possible to import form data as well as demographic data.
+
+* Conflicts encountered while importing cases or contacts can now optionally be
+  resolved by temporarily creating duplicate records, and then resolving the
+  duplicates via the duplicate person merging logic. A switch has been added to
+  the data import parameters to allow users to choose between ignoring
+  conflicting records or creating duplicate persons and cases. A new "View
+  import conflicts" option has been added for Admin users under Tools.
+
+Reports
+-------
+
+* Clicking on a row in report now takes you to the relevant case,
+  contact or form.
+
+* A shortcut has been added to the main page to allow reports with "Role" or
+  "Public" sharing to be run directly.
+
+
+Other changes
+-------------
+
+* It is now possible to create "Note" tasks that are not associated with any 
+  case or contact.
+
+* A (per instance) command line interface has been added to allow scripts to 
+  interact with the application. In particular, this allows scripted export of
+  data from an instance for analysis and reporting in other systems.
+
+* The duplicate person search is now configurable - in particular, the
+  inclusion and weighting of person demographic fields can be controlled, and
+  the user can choose between trigram or bigram matching.
+
+* Several other minor cosmetic and layout improvements have been made.
+
+* Several bug fixes and some refactoring to improve the robustness and 
+  maintainability of the program code. Many minor bugs in the notification
+  system have been fixed, although the effect of these will only be apparent if
+  a persistent application deployment model is used (for example, FastCGI).
+
+
+SUMMARY OF CHANGES IN VERSION 1.3.0 RELATIVE TO VERSION 1.2.0
+======================================================================
+
+* The ability to import demographic data from CSV or other delimited or
+  column-formatted data files has been enhanced and several bugs in the
+  previous version of this facility have been fixed. A name can now be
+  assigned to each source of data to be imported, and if the "Local ID"
+  field is assigned, this is assumed to be a unique key which can be used
+  to update records that have already been imported. This means that updated
+  versions of an imported demographic data can be re-imported and the changes
+  in that updated file will be applied to existing records, rather than a
+  duplicate set of records being created. Demographic data 
+  imported from external sources is marked as read-only, although users
+  can "take ownership" of such records if they need to make changes in
+  NetEpi (and such changes will not be over-written by future data imports
+  from that data source). The use-case (motivation) for this enhancement
+  is the need to be able to repeatedly import batches of demographic data 
+  from sources such as aircraft passenger manifest files, or from Disaster 
+  Victim Registration (DVR) databases, which are often operated by Police or 
+  other agencies in disaster and emergency situations. Note that deletion of
+  imported records which have disappeared from subsequent import data files
+  does not occur (this may be added in a future version).
+  
+* The batch duplicate person/case/contact detection facility has been made more
+  robust and now runs on demand "in the background", rather than interactively,
+  and the results of the last batch run of the duplicate detector are
+  displayed. This means that the duplicate detection system is now able to deal
+  with tens or hundreds of thousands of records if necessary. 
+
+* Installation options were added to allow customised login page and banner
+  logos to be used.
+
+* The installer is now smarter and doesn't reset previously specified options
+  (with a few exceptions - see the installation notes later in this file).
+  This makes the installation of upgrades and bug-fixes easier and less error-
+  prone.
+
+* The administrator interface for configuring demographic fields has been
+  improved, making it much quicker to use.
+
+* The tabbed interface to demographic fields can be disabled by the system 
+  administrator if required - for example, if only a small number of 
+  demographic fields have been enabled.
+
+* A few minor and obscure bugs were fixed, including a problem with the 
+  pop-up calendar when used with Microsoft Internet Explorer 6 and 7 web
+  browsers.
+
+* A utility has been added to create scatterplot graphs of the RTT (round-trip
+  time) performance data collected by NetEpi as it operates. These data give
+  an indication of the response times of individual end-users of a NetEpi
+  installation. This allows problems with their Internet or other network 
+  access to the NetEpi web site to be monitored and addressed pro-actively.
+  The script which extracts the RTT data from the web server logs is now 
+  smarter and can filter the data in various ways. 
+
+* An example of a separate, simple "labsurv" web application designed to 
+  collect weekly, aggregate surveillance data on the numbers and results of
+  tests for common respiratory viruses has been included under the labsurv/
+  directory of the NetEpi-Collection distribution tarball. Currently, NetEpi
+  Collection is designed to collect unit-record data about persons (cases or
+  contacts of a disease or syndrome), and although it can be (ab)used to 
+  collect aggregate data about "things", it is far from optimal for such 
+  purposes. The simple "labsurv" application, which uses the same infra-
+  structure as NetEpi Collection (i.e. Python, the Albatross web framework,
+  PostgreSQL) has been provided as both a template for similar use-cases else-
+  where, and as a test-bed for features and design approaches to be integrated
+  into future versions of NetEpi Collection. It should be possible for a
+  programmer familiar with Python to customise the labsurv application for
+  similar purposes in just a few days. In the future, we hope to provide the
+  ability for NetEpi admistrators to be able to  set up similar aggregate 
+  and/or "thing"-related (as opposed to person-related) data collection 
+  facilities with the usual NetEpi point-and-click interface.
+
+SUMMARY OF CHANGES IN VERSION 1.2.0 RELATIVE TO VERSION 1.1.0
+======================================================================
+
+The following changes have been made in this version:
+
+* The ability to import demographic data from CSV or other delimited or
+  column-formatted data files has been added. A "point-and-click" interface
+  to "map" columns in the file to be imported to target demographic
+  fields in NetEpi Collection is provided, and a range of 
+  transformations of data values can also be specified. Transformed data
+  can be previewed before it is imported. The parameters used to import
+  a particular data file can be saved and re-used on subsequent occasions.
+  
+* Several new fields were added to the core "demographics" fields:
+
+  - a third set of address fields, so there are now address fields 
+    for residential address, an alternate address, and a work or 
+    school address (although the field labels on all of these can
+    be customised as previously, so they can be used for other types
+    of addresses). [1]
+
+  - a Country field has been added to each of the three sets of address
+    fields.
+
+  - an Occupation field was added, grouped with the Work/School address
+    fields. [2]
+
+  - a second set of Passport Number and Passport Country fields were 
+    added to accommodate people with dual nationality. The default
+    label on the Passport Country fields was changed to "Passport
+    country/Nationality".
+
+  - fields to capture the name and contact details of the notifier of a
+    case or contact were added (these details are associated with the
+    case or contact and are thus stored on the underlying CASES table)
+
+  - an "Other Information" text box field was added to record other,
+    miscellaneous information about each person.
+
+  As previously, all of these fields and the other demographics fields
+  are configurable, and can be hidden or disable or set to appear in 
+  only certain parts of the application.
+
+* In order to better accommodate the increased number of demographics
+  core fields, a "tabbed" display for some of these fields has
+  been provided, which helps to avoid the need for too much vertical
+  scrolling. The tabbed field display is also provided on the search
+  parameter pages.
+
+* Two new data collection "input" types were added: a pull-down list
+  input for "Countries", which lists all current countries, and an equivalent
+  one for "Languages", which lists major languages.
+
+* Disabled input boxes are no longer used for the "read-only" display of
+  demographic data in certain places in the application; instead, an
+  easier-to-read and more compact format is used.
+
+* Searching for existing cases and contacts has been improved by providing
+  given name and nickname substitution when the "Phonetic" box is checked: 
+  for example, a search for "Tim" will also find "Timothy" and a search
+  for "Margaret" will find "Peggy" and so on.
+
+* The duplicate person matching algorithm has been improved (it now uses
+  trigrams instead of bigrams) and has been made faster.
+
+* Case and contact details are now colour-coded on the search results
+  pages in order to make visual scanning and parsing of the results easier.
+
+* "<<Back" buttons have been added to the top (in addition tot he foot) of
+  most pages.
+
+* The display of available contact definitions on the case contacts page
+  has been made consistent with the way in which case and contact definitions
+  are displayed on the home page.
+
+* The page is now scrolled down to any "Unsaved changes" warning messages
+  to avoid any confusion on long pages.
+
+* An "login_helpdesk_contact" configuration option has been added. The
+  option specifies an contact message that is used on the login and
+  new-user registration pages. This allows an alternate message to be
+  specified in these (potentially) less secure contexts.
+
+* Several other minor cosmetic and layout improvements have been made.
+
+* Several bug fixes and some refactoring to improve the robustness and 
+  maintainability of the program code.
+
+[1] We know that an unlimited number of addresses, including a complete
+address history, should ideally be stored in a separate ADDRESS table,
+but that requires fairly major refactoring of core parts of the application
+and will have to wait until version 2. The current arrangements seem to be
+adequate in the meantime (and changes to addresses are captured in the
+audit logs, although we understand that that is not a substitute for a
+full address history).
+
+[2] The Occupation column was added to the PERSONS table, for reasons
+of expedience. Ideally, it should be on the CASES table and reside 
+with other CASE data (as should some addresses and other data current stored
+in the PERSONS table). These structural changes are planned for version 2,
+but in the meantime, the current arrangements are unlikely to cause major 
+problems with most acute outbreak investigations or other relatively short-
+term epidemiological data collections, although we recognise that the current
+data model is not ideal for use by, say, a chronic disease registry.
+
+SUMMARY OF CHANGES IN VERSION 1.1.0 RELATIVE TO VERSION 1.0.4
+===============================================================
+
+The following changes have been made in this version:
+
+* The login page has been re-styled to be rather more attractive.
+
+* The previously separate administration application has been merged into
+  the main end-user application. Now there is only one URL for each NetEpi
+  Collection instance, and if a logged-in user has administrator rights,
+  then an additional menu item appears which allows them to access the
+  administration functions that were previously accessible only from a
+  separate admin URL. Apart from making things easier for administrators,
+  this change has enabled a reduction in otherwise duplicated code - thus
+  the total line count for the project has gone down substantially.
+
+* An internal "notification" mechanism has been added so that changes made in
+  the administration interface are immediately reflected in all sessions for
+  that NetEpi instance. Previously, due to caching, administrative changes
+  took up to ten minutes to propagate through to all user sessions, which
+  could be confusing for users and administrators alike. See also the notes
+  below on "Stand-alone Notification Daemon" regarding the need to disable
+  this facility in certain unusual server configurations.
+
+* Administrators can now specify the order in which syndromes/case/contact
+  definitions are listed on the home page.
+
+* Each instance of a data collection form is now assigned a unique ID,
+  which incorporates a check-digit, and this ID number is shown on screen
+  and appears in print-outs of forms and in data export files.
+
+* Users can specify a form instance ID number in the search page, and if it
+  is a valid form ID number and the user has permission to access that case
+  or contact, the form editing page for that form instance (and thus for
+  the correct person and case or contact record) will be displayed immediately.
+
+* A simple "epi curve" charting facility has been added to the built-in
+  reports. An "epi curve" is a frequency histogram by onset date (reporting
+  date can also be be chosen, or dual charts for both can be drawn). Various
+  date and time "binning" options are provided to control the degree of
+  temporal aggregation on the x-axis. Further features will be added to
+  this chart in later releases, and other built-in charts and graphs will
+  be added.  See also the additional installation prerequisites listed below
+  which relate to this new feature.
+
+* The search parameters which a user enters are now shown on all search results
+  pages. This is particularly important when adding a new case or contact,
+  because these search parameters provide context to the "Create a new case"
+  or "Create a new contact" buttons.
+
+* Users with "task queue admin" rights can now set up task queues specific to
+  their unit (or shared with other units or specific users) without needing
+  to ask a central administrator to do it for them.
+
+* Administrators can see summary statistics about the tasks allocated to each
+  task queue.
+
+* The refresh button on the end-user task listing page has been removed
+  (provided that Javascript is available and enabled in the user's browser).
+
+* Task filtering and display parameters for each user are automatically
+  restored and saved each time a user visits the task list page.
+
+* Built-in reporting parameters for each user are automatically restored
+  and saved, on a per case/contact definition basis, each time a user visits
+  the built-in reports facility.
+
+* User accounts are now only logically deleted, thus preserving entries
+  in the audit trail relating to deleted users. User accounts can also be
+  easily undeleted. The enabled/disabled user flag remains, unchanged, so
+  that user accounts can still be temporally disabled if necessary, and self-
+  registered user accounts are still created with the disabled flag set and
+  need to be deliberately enabled by an administrator.
+
+* Additional checks are now made before a unit can be deleted, and units with
+  enabled users assigned to them can't be deleted. Previously, if units with
+  active users were deleted, the users could be left with no unit membership,
+  which prevented them from logging in.
+
+* Deleting work queues is now possible. Database integrity constraints 
+  previously prevented this except in the case of work queues that had never
+  had any tasks associated with them. Work queues can now be deleted (after
+  confirmation), provided that all associated tasks have been completed.
+
+* Previously, there were a number of places in the application in which 
+  administrators could accidentally exit an admin page without being warned
+  they had unsaved changes. This has now been addressed.
+
+* More useful configuration and debug information is included in tracebacks
+  if an application exception occurs.
+
+* Authenticated user id, unit id and the NetEpi instance name are now included
+  in the RTT (round-trip-time) "perceived performance" data written to the
+  web server log for each user interaction. This makes identification of
+  network performance issues affecting individual users or groups of users
+  at a particular location much easier.
+
+* After much experimentation, problems with unexpected behavior when using
+  the browser back buttons on the Opera and Konqueror web browsers have now
+  been addressed. The browser back button now behaves as expected (that is,
+  the same way that it behaves when Gecko-based browsers such as Mozilla and
+  Firefox, or Microsoft Internet Explorer version 6 or 7 are used). In the
+  Safari browser, the  browser back button is now effectively disabled when
+  accessing NetEpi. This is an improvement on the unexpected behaviour of the
+  browser back button in Safari browser when accessing previous versions of
+  NetEpi. It should be noted that many web applications have major difficulties
+  with the non-standard behaviour of the browser back button in the Safari
+  browser (and possibly in the related WebKit browser engine). Of course,
+  Safari users can still use the "<<Back" buttons provided in the NetEpi
+  application: these work exactly as expected in Safari and in all other
+  Web browsers.
+
+* Numerous minor bug fixes and other minor cosmetic adjustments have been
+  made.
+
+* Updates to technical and system documentation.
+
+SUMMARY OF CHANGES IN VERSION 1.0.4 RELATIVE TO VERSION 1.0.3
+===============================================================
+
+Version 1.0.4 provides the ability to set the preferred date input and
+output format on an instance-wide basis, and/or by individual users. Three
+date formats can be specified: DMY (DD/MM/YYYY format), MDY (MM/DD/YYYY
+format) or ISO (YYYY-MM-DD format). All dates and date/time values are
+stored in the database in a format-neutral manner, thus there is no
+problem if different users of the same NetEpi database instance use
+different preferences for the date format, and individual users can
+change their preferred date format on-the-fly without problems.
+
+This feature, together with customisable state/territory/region fields
+introduced in Version 1.0.3 and the ability to customise the labels of the
+demographic fields added to Version 0.94, should allow NetEpi Collection
+to be more easily adapted for use in English-speaking locales outside
+Australia. A fully internationalised version accommodating non-English
+locales is possible but collaborators are required - the current NetEpi
+team cannot create such a fully-internationalised version without
+assistance, although they would very much like to do so.
+
+Additionally, Version 1.0.4 fixes several minor bugs - a list of
+fixes can be found in the doc/CHANGELOG file. Support for a new
+Python-to-PostgreSQL adaptor being developed by Object Craft Pty Ltd
+(http://www.object-craft.com.au) has been added. The free, open-source
+Object Craft adaptor is still be tested but when released, NetEpi
+Collection installations running Version 1.0.4 or later will automatically
+make use of it if it is installed.  The release of the Object Craft
+Python-to-PostgreSQL adaptor, which promises to be considerably faster
+than existing Python-to-PostgreSQL adaptors, will be announced on the
+netepi-discuss mailing list as well as elsewhere.
+
+Finally, a PDF version of a presentation on NetEpi Collection and
+NetEpi Analysis, given at the School of Information Technologies
+at the University of Sydney in June 2007 has been included in
+documentation section of the NetEpi page on SourceForge - see
+http://sourceforge.net/docman/?group_id=123700
+
+
+SUMMARY OF CHANGES IN VERSION 1.0.3 RELATIVE TO VERSION 1.0.2
+===============================================================
+
+Version 1.0.3 provides the ability to customise the list of geographical
+states, territories and regions in the address part of the demographics
+section.  The global list of states and territories for an entire instance
+of NetEpi Collection can be customised, and this can be further customised
+on a per-syndrome or per-case-definition basis if required. The customised
+lists of states and territories are automatically taken into account
+when performing general searches.
+
+Additionally, Version 1.0.3 fixes a small number of minor but annoying
+bugs - a list of fixes can be found in the doc/CHANGELOG file. Special
+thanks to Stefan Stirzaker, Office of Health Protection, Surveillance
+Branch, Surveillance Policy & Systems Section, Population Health Division,
+Australian Government Department of Health and Ageing, for reporting
+the bugs in question.
+
+
+SUMMARY OF CHANGES IN VERSION 1.0.2 RELATIVE TO VERSION 1.0beta
+===============================================================
+
+This is a bug-fix release only, no new features or facilities have
+been added.  A list of fixes and other minor cosmetic changes can be
+found in the doc/CHANGELOG file.
+
+Special thanks to Francois Marsan for finding and carefully reporting a
+number of obscure and not-so-obscure bugs that had eluded us.
+
+
+SUMMARY OF CHANGES IN VERSION 1.0beta RELATIVE TO VERSION 0.99
+==============================================================
+
+The major addition to Version 1.0beta is the inclusion of a comprehensive
+record merging facility. This provides a mechanism to identify potentially
+duplicated PERSON records (using a bigram-based fuzzy matching algorithm,
+although arbitrary pairs of records can also be merged), and the user can
+then selectively merge demographic details and associated case, contact
+and task records, for each pair of duplicated person records. This may
+result in duplicated case or contact records for the resulting "merged"
+person, so there is an additional facility which can be used to merge
+case or contact records for a given person. Similarly for each case or
+contact record, there is a facility to merge data form records of the
+same type. In these merge facilities, each of which can be accessed
+independently of the others, the user can select whether to retain
+data from "record A" or "record B", on a data-item-by-data-item basis,
+or may chose to include information from both A and B records and edit
+the resulting combined data field.  Much effort was put into designing a
+simple user interface to these merge facilities which make the complex
+merging task as quick and easy as possible for the end user. Unlike
+deletions, merges of records cannot be undone, but full details of
+all merge actions are captured in the audit trail log, allowing manual
+separation of merged records if required subsequently.
+
+Another major change in this version is the renaming of the application
+from "NetEpi Case Manager" to "NetEpi Collection", which better reflects
+the fairly generic role it plays, while also sounding less clinical.
+
+Other significant enhancements to Version 1.0beta include:
+
+* Provision of a read-only mode, in which the privileges of selected 
+  users can be restricted so that they can only view data, but not
+  edit or delete it. The usual fine-grained access control mechanisms 
+  still apply to such read-only users.
+* Pull-down menus have replaced a plethora of buttons on the case 
+  and contact editing pages. The result is a cleaner look with no loss
+  in usability (we feel, feedback welcome).
+* Labelling of cases versus contacts has been made smarter in order to
+  reduce the occasional semantic confusion that was evident in parts 
+  of the application.
+* On certain pages more contextual information with respect to the 
+  current user or current case or contact has been provided to assist
+  users in regaining their "bearings" after being interrupted while using
+  the system.
+* Visual feedback is now provided each time a record of any type is updated.
+* Most error messages and warnings now appear at the top of the page, 
+  where they are harder to overlook.
+* Access to the built-in line-listing/reports facility is now restricted
+  to those users who have been granted the "bulk export" privilege.
+* The "Next" button on search pages is now the default, so that the 
+  Enter key can be used to cause a search to be performed (thus
+  improving end user ergonomics).
+* The "round-trip-time" facility, which measures and captures the 
+  response time (including network-related delays and latency) of NetEpi 
+  from the point-of-view of the end user, has been enhanced to work
+  correctly when the Microsoft Internet Explorer browser is used, and now
+  also records a timestamp for each response time captured in the web server
+  log. Utilities to extract these response times from the web server log 
+  for analysis and presentation in statistical or other visualisation 
+  packages have also been added, including a script to import the data
+  into NetEpi Analysis.
+* Another utility, called "httpinteract", has been included. It is
+  a simple tool which can be used to acquire time-series data about
+  network performance from end-user's workstations or access points.
+  We have found it useful in pinpointing when and where network bandwidth,
+  latency or congestion may be inadequate for satisfactory use of NetEpi,
+  so that appropriate remedial action can be taken - such network problems
+  can almost always be solved.
+* The deletion status of records is clearly shown when they are printed
+  out.
+* Facilities to browse all cases for a given person, and to jump directly
+  to the parent case of a contact, were added.
+* Unit tests and some Selenium functional tests were updated to work
+  correctly with the current version.
+* System documentation was brought up-to-date.
+
+Finally, a lot of work has also gone into the creation of a "live CD"
+for demonstration purposes. This "live CD", which is based on the popular
+Ubuntu Linux distribution, allows most recent (that is, less than four
+or five years old) desktop or laptop computers to be booted directly
+from the live CD into a cut-down version of Ubuntu Linux, with a full,
+working copy of NetEpi Collection installed and ready to use. Data can be
+stored between sessions on a USB memory stick or external hard drive. No
+software or files are actually installed on the host computer - it runs
+entirely from the CD-ROM. The demonstration system includes a full Web
+server and can thus be accessed over a network by multiple users, as well
+as locally.  Introductory "screencasts" and other background material will
+be included on the final NetEpi live CD. However, at this stage the live
+CD is still undergoing testing, but we have included the scripts needed
+to build the live CD in the NetEpi Collection distribution tarball for
+those who wish to create their own customised version. However, these
+scripts are complex and dangerous, and should only be run and modified
+by experienced system administrators and developers who know exactly
+what they are doing. They are only needed to create new versions of a
+live CD and should be completely ignored by NetEpi users and most system
+administrators. An .iso file of a demo live CD will be made available
+for download (and then burning onto a CD-ROM) at the time of the final
+release of Version 1.0.
+
+
+SUMMARY OF CHANGES IN VERSION 0.99 RELATIVE TO VERSION 0.98
+===========================================================
+
+Apart from several minor bug fixes, some general cleaning up of the code
+base and some additional unit tests, the very big addition to Version
+0.99 is a built-in "line-listing" reporting facility.
+
+This facility allows tabular "line-listing" reports to be produced
+through a simple and easy-to-use interface. Filters and sorting orders 
+based on demographic data and on data items contained in syndrome/case
+definition-specific forms can be defined, and the resulting record sets 
+appear in the report or can be used as a "browse list" to page through
+case or contact records in the edit screens, one at a time. Summary 
+or user-selectable fields (columns) from forms can also be included
+in the reports.
+
+The (still experimental) case-to-contact relationship visualisation tool 
+has also been integrated into the reporting framework to take advantage
+of the filtering facilities it provides.
+
+Report definitions (including the filtering parameters) can be saved
+for re-use later, and these saved report definitions can be shared 
+with other members of the user's business unit or with all users 
+on the system.
+
+
+SUMMARY OF CHANGES IN VERSION 0.98 RELATIVE TO VERSION 0.97
+===========================================================
+
+Version 0.98 brings the following (in no particular order): 
+
+* A (global) count of active cases of each syndrome is now
+  shown on the home page.
+
+* An indication of number of contacts is now shown on the 
+  main case edit page.
+
+* The ability to examine the details of users in each
+  business unit from task assignment and ACL (access control
+  list) pages.
+
+* All current and enabled syndromes/case definitions are now
+  shown on the main page, not just those the unit (or rather
+  users in that unit) has access to, but the "add" button is
+  suppressed if the unit does not have edit rights for that 
+  syndrome/case definition.
+
+* A facility to allow NetEpi administrators to "clear" a 
+  syndrome/case definition of data - this deletes all cases,
+  contacts, form instances (but not form definitions!) and
+  tasks associated with a syndrome/case definition (without
+  deleting the syndrome/case definition itself). This is very
+  useful for clearing out training or test data from a 
+  syndrome/case definition prior to "production" or real-life
+  use. An additional confirmation was added because this 
+  facility permanently deletes data.
+
+* A facility by which a NetEpi administrator can use the web 
+  interface to permanently delete a syndrome/case definition
+  and all associated cases, forms, tasks and log entries. This
+  allows redundant, obsolete or test/dummy syndromes/case
+  definitions to be removed from an existing NetEpi instance,
+  or for a NetEpi instance to be cloned (using PostgreSQL 
+  database back-up and restore utilities) and then unwanted 
+  syndromes/case definitions removed. An additional confirmation
+  was added because this facility permanently deletes data.
+
+* No longer indicate nature of login failures to improve
+  security.
+
+* Add additional demographic fields for one extra set of 
+  alternative address and other contact information (which
+  can be relabelled for each case definition/syndrome)
+ 
+* Improvements to form definition importing (form definition
+  import name matching a little smarter, make form import seed
+  the "forms" table if necessary).
+
+* A new command line facility to import and export form definitions
+  in batches from a Collection instance.
+
+* "Onset date" and "Notification date" error messages now
+  honour syndrome-specific labelling for these fields.
+
+* The ability to view logs associated with a contact.
+
+* The main application structure graph was updated, and an admin
+  application structure graph was added.
+
+SUMMARY OF CHANGES IN VERSION 0.97 RELATIVE TO VERSION 0.96
+===========================================================
+
+The major additions to Version 0.97 are: 
+
+* Ability to add a contact record and post-hoc associate it with 
+  a case, and/or dissociate it from a case and re-assign to a different
+  case, and handle the merging of ACL (access rights) necessitated
+  by such changes.
+* Improvements to the form data roll-forward facility so that 
+  incompatible changes to form filed data types that would lead
+  to data loss or truncation can be readily identified and corrected.
+* Ability to print the data associated with a case or contact as a
+  "report" (rather like a set a already-filled-in forms)
+* Ability to optionally include or exclude deleted records from 
+  data exports
+* Restriction of ability to export data to only some users who 
+  have been explicitly granted the right to do so.
+* Improvements to the information captured in the audit log,
+  particularly with respect to contact records and their association
+  with case records.
+* Require confirmation when undeleting cases or contacts.
+* Require confirmation needed when deleting a user in the admin
+  application.
+* Fixed bug when a users from a disabled unit tried to log in.
+* Simplification of conditional question skip/enable subsystem.
+* Additional guards against inadvertent failure to save changes in 
+  admin interface
+
+In addition, numerous minor cosmetic changes and bug fixes have been 
+implemented.
+
+SUMMARY OF CHANGES IN VERSION 0.96 RELATIVE TO VERSION 0.95
+===========================================================
+
+The major addition to Version 0.96 is a conditional form input skip/enable
+feature, which allows parts of questions, whole questions or multiple
+questions on a form to be selectively enabled or disabled based on
+categorical data elsewhere on the same form. Skip instructions are
+automatically generated for printed forms or where Javascript is
+unavailable or disabled.
+
+A large improvement to usability is also provided by some clever
+JavaScript which captures the "Back" button on the user's browser and
+makes it behave like the "<<Back"" navigation buttons in the application
+itself. This effectively removes a major source of user frustration by
+avoiding browser-generated "re-post" dialogue boxes appearing on screen.
+
+In addition, a large number of bug fixes have been made and the interface
+to parts of the admin application has been made more consistent and
+foolproof - it is now very hard to inadvertently forget to save changes
+made in the admin application. Also, the question editor pages in the
+admin form definition editor facility has been revised.
+
+
+SUMMARY OF CHANGES IN VERSION 0.95 RELATIVE TO VERSION 0.94
+===========================================================
+
+The major addition to Version 0.95 is a simple (but we hope effective)
+workflow subsystem, which allows "tasks", such as completing a case or
+contact follow-up form, to be assigned to individual users, business
+units or arbitrary "task queues" to which users and business units can
+be subscribed.  These tasks can be scheduled for immediate or delayed
+attention, and users have access to a list of tasks assigned to them
+or otherwise available to them through their membership of a business
+unit or subscription to a task queue.  Clicking on an assigned task
+automatically loads the correct case or contact record and opens the
+correct data form to be completed. Additional instructions can be added
+to tasks if necessary.
+
+This subsystem is intended to allow tasks generated by a large number
+of cases or contacts of cases of, say, a novel strain of influenza to
+be distributed to many workers located in many business units across
+the health system. It should also be useful for many other purposes,
+including reminders to oneself to follow-up missing data or check
+responses and so on.
+
+Other features added to Version 0.95 include better visual feedback when
+data in forms is updated, better indication of the deletion status of
+records, improvements to the form editor, and various minor cosmetic
+and labelling improvements.
+
+SUMMARY OF CHANGES IN VERSION 0.94 RELATIVE TO VERSION 0.90
+===========================================================
+
+Following is a summary, in no particular order, of the major changes 
+visible to or of relevance to end-users and/or administrators, in 
+NetEpi Collection Version 0.94, relative to NetEpi Collection
+Version 0.90 which was released in June 2005. Note that Versions 0.91
+and 0.92 were intermediate versions which were not released publicly
+and Version 0.93 was quickly superseded by Version 0.94.
+
+In addition, there has been considerable refactoring and general tidying-up
+of the underlying programme code, as well as several bug fixes and security
+improvements. 
+
+ * Problem with download of exported data files via Microsoft Internet
+   Explorer fixed.
+
+ * Added a new class of user (called "unit admins") who can administer
+   users in their unit via a new page accessible from the end-user (not
+   admin) application. This removes a potential bottleneck of admins
+   having to remotely vet and approve large numbers of new user accounts
+   in a short time during an emergency.
+
+ * Added JavaScript to track and report client's submit RTT
+   (round-trip-time), added code to Request object to log reported
+   RTT. This allows the total response time, as perceived by the end
+   user, to be monitored centrally - in particular, problems related to
+   network congestion can be detected, as well as problems due excessive
+   server load.
+
+ * Added the ability to print selectable sets of blank forms, on
+   a per-syndrome basis, for use if the system is off-line, down or
+   otherwise unavailable.
+
+ * Ability to add certain rights to groups of users, and also ability
+   to add certain rights to individual users. These include the right
+   to see or edit all records.
+
+ * Version information added to login screens to help avoid confusion
+   during support calls.
+
+ * Ability to save user preferences, including preferences for phonetic
+   searching, results per page and pop-up calendars (see below).
+
+ * Added client-side sorting of various tables in the admin application.
+
+ * JavaScript used to to scroll to first error in forms.
+
+ * Form definitions are now stored in XML form in the main database, not
+   as Python files in the Web server file system. This improves security
+   and makes it much easier to back-up the entire state of the system,
+   without having to interrupt use of the system in any way.
+
+ * Added ability to export and import form definitions in XML form, which
+   lays the foundations for a shared and/or distributed form definition
+   "library".
+
+ * Form inputs that are required (mandatory) are now visually identified.
+
+ * Added "definition last updated" and other useful metadata to form
+   definitions.
+
+ * Merge demogfields-branch
+
+ * Added additional demographic/ID fields: mobile_phone, fax_phone,
+   e_mail, passport_number and passport_country, interpreter
+   required/language, to make the system better suited for use in border
+   screening at airports etc.
+
+ * Added ability to relabel or hide demographic/ID fields on a
+   per-syndrome basis.
+
+ * Added per-syndrome case status field, renamed field to just "status".
+
+ * Storage of case and contact data unified. This allows a great deal
+   more flexibility, including the ability to specify multiple "contact
+   syndromes" for different types of contacts of cases.
+
+ * Added interactive pop-up date entry widget (calendar).
+
+ * Allowed allow a person to be contact of a case for more than one
+   contact syndrome; included contact type in search results; revised
+   styling of search results for cases and contacts to make them slightly
+   clearer.
+
+ * Simplified application navigation slightly (removed user details
+   link from page banner, use tools instead), carry more context across
+   new/edit/search screens.
+
+ * Added paging to contacts list to allow cases with large numbers of
+   contacts to be more easily managed.
+
+ * Added experimental Case/Contact relationship visualisation using
+   GraphViz.
+
+ * Changes to allow PostgreSQL versions 8.0 and 8.1 to be used, as well
+   as versions 7.3 and 7.4 as supported previously.
+
+ * Deleting/hiding of cases and contacts now fully implemented.
+
+ * Major speed improvements to phonetic searches.
+
+ * Updated Collection-ER documentation to reflect schema changes.
+
+ * Added a confirmation dialog when admins enable a user, to ensure that
+   they have checked the new user's bona fides.
+
+ * Add logging of case access (ACL) control changes.
+
+ * Added support for use of Trac-style wiki mark-up for text used in
+   various places in the application (see doc/NetEpi_wiki_markup.html ).
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..bba3c95
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,32 @@
+NetEpi Collection was developed by Tim Churches of the Centre for
+Epidemiology and Research, New South Wales Department of Health (Health
+Administration Corporation, New South Wales), and by Andrew McNamara of Object
+Craft Pty Ltd, Melbourne, working under contract to the New South Wales
+Department of Health. James Farrow of Farrow Norris Pty Ltd, also 
+working under contract to the New South Wales Department of Health, also
+contributed to the work. All work on NetEpi Collection performed by McNamara,
+Farrow and Churches is copyright to the NSW Department of Health under the 
+terms of the contractual arrangements covering their respective engagements by
+the NSW Department of Health.
+
+The Australian Government Department of Health and Ageing sponsored the
+following enhancements to version 0.15 of NetEpi Collection:
+
+  * Added function to export cases/person and case forms as a row
+    per case CSV file (and associated unit tests).
+  * Added new "local case id" case field, re-label "case id" as
+    "system case id".
+  * Parameterised "syndrome label" -> "Case Definition".
+  * Miscellaneous cosmetic/presentation changes.
+  * Miscellaneous bug fixes.
+
+The Australian Government Department of Health and Ageing also sponsored
+extensive enhancements to the reporting system for version 1.7 of NetEpi
+Collection.
+
+Some third-party programme code and software components, all of which are 
+licensed under free, open source licences which are compatible with that used
+by NetEpi Collection, have also been incorporated into NetEpi Collection
+and are included as part of the NetEpi Collection distribution. Details of
+these inclusions are provided in the LICENCE file.
+
diff --git a/LGPL.txt b/LGPL.txt
new file mode 100644
index 0000000..5ab7695
--- /dev/null
+++ b/LGPL.txt
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..07fbd6d
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,603 @@
+COPYRIGHT AND LICENSING ARRANGEMENTS
+====================================
+
+All material is copyright 2004-2010 Health Administration Corporation
+(New South Wales Department of Health), Australian Government Department
+of Health and Ageing, and others, except where otherwise indicated in
+the header section of the specific files listed below.
+
+Except for the files listed below, NetEpi Collection is licensed
+under the terms of the Health Administration Corporation Open Source
+Licence V1.2 (HACOS Licence V1.2), the complete text of which appears
+below.
+
+The following files, included in the NetEpi Collection distribution,
+are copyright to the owners and are licensed under the terms indicated
+in the following list. Please see the file header for each of these
+files for further details.
+
+Elements of 'The Coolest DHTML Calendar'
+http://sourceforge.net/projects/jscalendar
+Copyright Mihai Bazon, licensed under the LGPL:
+	casemgr/app/calendar.css
+	casemgr/app/lang/calendar-en.js
+	casemgr/app/calendar.js
+
+"Safe HTML" code
+Copyright Object Craft Pty Ltd, licensed under the 
+Object Craft License (similar to the BSD licence),
+details available from http://www.object-craft.com.au:
+        cocklebur/safehtml.py
+
+Phonetic encoding routines from the FEBRL project
+(a collaborative project between Australian National University
+and the Centre for Epidemiology and Research, NSW Department of Health,
+see http://datamining.anu.edu.au/projects/linkage.html ), under the
+ANUOS License V1.1, which is almost identical to the HACOS V1.2 license
+used by NetEpi Collection:
+        casemgr/phonetic_encode.py
+
+Elements of the Trac system (enhanced wiki and issue tracking system for
+software development projects) by Edgewall Software (see 
+http://trac.edgewall.org/ ), licensed under the Trac licence (a modified 
+BSD licence), the full text of which can be found in the Trac_licence.txt
+file included in the this NetEpi Collection distribution. The files
+covered by the Trac licence are:
+        wiki/api.py
+	wiki/core.py
+	wiki/env.py
+	wiki/formatter.py
+	wiki/href.py
+	wiki/log.py
+	wiki/util.py
+	app/app/wiki.css
+
+The Javascript code used to control the behaviour of the browser "Back"
+button  was initially inspired by the dojo.io.bind() function in the
+Dojo Javascript toolkit (http://www.dojotoolkit.org/), as described
+in more detail by Alex Russell at http://alex.dojotoolkit.org/?p=479.
+However, no Javascript code from the Dojo toolkit was re-used - all
+code was re-written de novo. Brad Neuberg's notes about the underlying
+technique at
+http://codinginparadise.org/weblog/2005/08/ajax-tutorial-tale-of-two-iframes-or.html
+were also found to be useful, but again no Javascript code was re-used
+or borrowed. The relevant file is:
+        app/app/nobbleback.js	
+
+-----------------------------------------------------------------
+
+HEALTH ADMINISTRATION CORPORATION OPEN SOURCE LICENCE VERSION 1.2
+=================================================================
+
+1. DEFINITIONS. 
+
+   "Commercial Use" shall mean distribution or otherwise making the
+   Covered Software available to a third party.
+
+   "Contributor" shall mean each entity that creates or contributes to
+   the creation of Modifications.
+
+   "Contributor Version" shall mean in case of any Contributor the
+   combination of the Original Software, prior Modifications used by a
+   Contributor, and the Modifications made by that particular Contributor
+   and in case of Health Administration Corporation in addition the
+   Original Software in any form, including the form as Executable.
+
+   "Covered Software" shall mean the Original Software or Modifications
+   or the combination of the Original Software and Modifications, in
+   each case including portions thereof.
+
+   "Electronic Distribution Mechanism" shall mean a mechanism generally
+   accepted in the software development community for the electronic
+   transfer of data.
+
+   "Executable" shall mean Covered Software in any form other than
+   Source Code.
+
+   "Initial Developer" shall mean the individual or entity identified as
+   the Initial Developer in the Source Code notice required by Exhibit A.
+
+   "Health Administration Corporation" shall mean the Health
+   Administration Corporation as established by the Health Administration
+   Act 1982, as amended, of the State of New South Wales, Australia. The
+   Health Administration Corporation has its offices at 73 Miller Street,
+   North Sydney, New South Wales 2059, Australia.
+
+   "Larger Work" shall mean a work, which combines Covered Software or
+   portions thereof with code not governed by the terms of this Licence.
+
+   "Licence" shall mean this document.
+
+   "Licensable" shall mean having the right to grant, to the maximum
+   extent possible, whether at the time of the initial grant or
+   subsequently acquired, any and all of the rights conveyed herein.
+
+   "Modifications" shall mean any addition to or deletion from the
+   substance or structure of either the Original Software or any previous
+   Modifications. When Covered Software is released as a series of files,
+   a Modification is:
+
+   a) Any addition to or deletion from the contents of a file
+      containing Original Software or previous Modifications.
+
+   b) Any new file that contains any part of the Original Software or
+      previous Modifications.
+
+   "Original Software" shall mean the Source Code of computer software
+   code which is described in the Source Code notice required by Exhibit
+   A as Original Software, and which, at the time of its release under
+   this Licence is not already Covered Software governed by this Licence.
+
+   "Patent Claims" shall mean any patent claim(s), now owned or hereafter
+   acquired, including without limitation, method, process, and apparatus
+   claims, in any patent Licensable by grantor.
+
+   "Source Code" shall mean the preferred form of the Covered Software
+   for making modifications to it, including all modules it contains,
+   plus any associated interface definition files, scripts used to
+   control compilation and installation of an Executable, or source
+   code differential comparisons against either the Original Software or
+   another well known, available Covered Software of the Contributor's
+   choice. The Source Code can be in a compressed or archival form,
+   provided the appropriate decompression or de-archiving software is
+   widely available for no charge.
+
+   "You" (or "Your") shall mean an individual or a legal entity exercising
+   rights under, and complying with all of the terms of, this Licence or
+   a future version of this Licence issued under Section 6.1. For legal
+   entities, "You" includes an entity which controls, is controlled
+   by, or is under common control with You. For the purposes of this
+   definition, "control" means (a) the power, direct or indirect,
+   to cause the direction or management of such entity, whether by
+   contract or otherwise, or (b) ownership of more than fifty per cent
+   (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. SOURCE CODE LICENCE. 
+
+2.1 Health Administration Corporation Grant. 
+
+Subject to the terms of this Licence, Health Administration Corporation
+hereby grants You a world-wide, royalty-free, non-exclusive licence,
+subject to third party intellectual property claims:
+
+a) under copyrights Licensable by Health Administration Corporation
+   to use, reproduce, modify, display, perform, sublicense and
+   distribute the Original Software (or portions thereof) with or without
+   Modifications, and/or as part of a Larger Work;
+
+b) and under Patents Claims infringed by the making, using or selling
+   of Original Software, to make, have made, use, practice, sell, and
+   offer for sale, and/or otherwise dispose of the Original Software
+   (or portions thereof).
+
+c) The licences granted in this Section 2.1(a) and (b) are effective
+   on the date Health Administration Corporation first distributes
+   Original Software under the terms of this Licence.
+
+d) Notwithstanding Section 2.1(b) above, no patent licence is granted:
+   1) for code that You delete from the Original Software; 2) separate
+   from the Original Software; or 3) for infringements caused by: i)
+   the modification of the Original Software or ii) the combination of
+   the Original Software with other software or devices.
+
+2.2 Contributor Grant. 
+
+Subject to the terms of this Licence and subject to third party
+intellectual property claims, each Contributor hereby grants You a
+world-wide, royalty-free, non-exclusive licence:
+
+a) under copyrights Licensable by Contributor, to use, reproduce,
+   modify, display, perform, sublicense and distribute the Modifications
+   created by such Contributor (or portions thereof) either on an
+   unmodified basis, with other Modifications, as Covered Software and/or
+   as part of a Larger Work; and
+
+b) under Patent Claims necessarily infringed by the making, using,
+   or selling of Modifications made by that Contributor either alone
+   and/or in combination with its Contributor Version (or portions of
+   such combination), to make, use, sell, offer for sale, have made,
+   and/or otherwise dispose of: 1) Modifications made by that Contributor
+   (or portions thereof); and 2) the combination of Modifications made
+   by that Contributor with its Contributor Version (or portions of
+   such combination).
+
+c) The licences granted in Sections 2.2(a) and 2.2(b) are effective
+   on the date Contributor first makes Commercial Use of the Covered
+   Software.
+
+d) Notwithstanding Section 2.2(b) above, no patent licence is granted:
+   1) for any code that Contributor has deleted from the Contributor
+   Version; 2) separate from the Contributor Version; 3) for infringements
+   caused by: i) third party modifications of Contributor Version or ii)
+   the combination of Modifications made by that Contributor with other
+   software (except as part of the Contributor Version) or other devices;
+   or 4) under Patent Claims infringed by Covered Software in the absence
+   of Modifications made by that Contributor.
+
+3. DISTRIBUTION OBLIGATIONS. 
+
+3.1 Application of Licence. 
+
+The Modifications which You create or to which You contribute are governed
+by the terms of this Licence, including without limitation Section
+2.2. The Source Code version of Covered Software may be distributed
+only under the terms of this Licence or a future version of this Licence
+released under Section 6.1, and You must include a copy of this Licence
+with every copy of the Source Code You distribute. You may not offer or
+impose any terms on any Source Code version that alters or restricts the
+applicable version of this Licence or the recipients' rights hereunder.
+
+3.2 Availability of Source Code. 
+
+Any Modification which You create or to which You contribute must be made
+available in Source Code form under the terms of this Licence either on
+the same media as an Executable version or via an accepted Electronic
+Distribution Mechanism to anyone to whom you made an Executable version
+available; and if made available via Electronic Distribution Mechanism,
+must remain available for at least twelve (12) months after the date it
+initially became available, or at least six (6) months after a subsequent
+version of that particular Modification has been made available to
+such recipients. You are responsible for ensuring that the Source Code
+version remains available even if the Electronic Distribution Mechanism
+is maintained by a third party.
+
+3.3 Description of Modifications. 
+
+You must cause all Covered Software to which You contribute to contain
+a file documenting the changes You made to create that Covered Software
+and the date of any change. You must include a prominent statement that
+the Modification is derived, directly or indirectly, from Original
+Software provided by Health Administration Corporation and including
+the name of Health Administration Corporation in (a) the Source Code,
+and (b) in any notice in an Executable version or related documentation
+in which You describe the origin or ownership of the Covered Software.
+
+3.4 Intellectual Property Matters 
+
+a) Third Party Claims. 
+
+   If Contributor has knowledge that a licence under a third party's
+   intellectual property rights is required to exercise the rights
+   granted by such Contributor under Sections 2.1 or 2.2, Contributor
+   must include a text file with the Source Code distribution titled
+   "LEGAL'' which describes the claim and the party making the claim
+   in sufficient detail that a recipient will know whom to contact. If
+   Contributor obtains such knowledge after the Modification is made
+   available as described in Section 3.2, Contributor shall promptly
+   modify the LEGAL file in all copies Contributor makes available
+   thereafter and shall take other steps (such as notifying appropriate
+   mailing lists or newsgroups) reasonably calculated to inform those
+   who received the Covered Software that new knowledge has been obtained.
+
+b) Contributor APIs. 
+
+   If Contributor's Modifications include an application programming
+   interface (API) and Contributor has knowledge of patent licences
+   which are reasonably necessary to implement that API, Contributor
+   must also include this information in the LEGAL file.
+
+c) Representations. 
+
+   Contributor represents that, except as disclosed pursuant to Section
+   3.4(a) above, Contributor believes that Contributor's Modifications are
+   Contributor's original creation(s) and/or Contributor has sufficient
+   rights to grant the rights conveyed by this Licence.
+
+3.5 Required Notices. 
+
+You must duplicate the notice in Exhibit A in each file of the Source
+Code. If it is not possible to put such notice in a particular Source
+Code file due to its structure, then You must include such notice in a
+location (such as a relevant directory) where a user would be likely to
+look for such a notice. If You created one or more Modification(s) You
+may add your name as a Contributor to the notice described in Exhibit
+A. You must also duplicate this Licence in any documentation for the
+Source Code where You describe recipients' rights or ownership rights
+relating to Covered Software. You may choose to offer, and to charge a
+fee for, warranty, support, indemnity or liability obligations to one or
+more recipients of Covered Software. However, You may do so only on Your
+own behalf, and not on behalf of Health Administration Corporation or any
+Contributor. You must make it absolutely clear that any such warranty,
+support, indemnity or liability obligation is offered by You alone,
+and You hereby agree to indemnify Health Administration Corporation and
+every Contributor for any liability incurred by Health Administration
+Corporation or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+3.6 Distribution of Executable Versions. 
+
+You may distribute Covered Software in Executable form only if the
+requirements of Sections 3.1-3.5 have been met for that Covered Software,
+and if You include a notice stating that the Source Code version of the
+Covered Software is available under the terms of this Licence, including
+a description of how and where You have fulfilled the obligations of
+Section 3.2. The notice must be conspicuously included in any notice in
+an Executable version, related documentation or collateral in which You
+describe recipients' rights relating to the Covered Software. You may
+distribute the Executable version of Covered Software or ownership rights
+under a licence of Your choice, which may contain terms different from
+this Licence, provided that You are in compliance with the terms of this
+Licence and that the licence for the Executable version does not attempt
+to limit or alter the recipient's rights in the Source Code version from
+the rights set forth in this Licence. If You distribute the Executable
+version under a different licence You must make it absolutely clear
+that any terms which differ from this Licence are offered by You alone,
+not by Health Administration Corporation or any Contributor. You hereby
+agree to indemnify Health Administration Corporation and every Contributor
+for any liability incurred by Health Administration Corporation or such
+Contributor as a result of any such terms You offer.
+
+3.7 Larger Works. 
+
+You may create a Larger Work by combining Covered Software with other
+software not governed by the terms of this Licence and distribute the
+Larger Work as a single product. In such a case, You must make sure the
+requirements of this Licence are fulfilled for the Covered Software.
+
+4. INABILITY TO COMPLY DUE TO STATUTE OR REGULATION. 
+
+If it is impossible for You to comply with any of the terms of this
+Licence with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with the
+terms of this Licence to the maximum extent possible; and (b) describe the
+limitations and the code they affect. Such description must be included
+in the LEGAL file described in Section 3.4 and must be included with all
+distributions of the Source Code.  Except to the extent prohibited by
+statute or regulation, such description must be sufficiently detailed
+for a recipient of ordinary skill to be able to understand it.
+
+5. APPLICATION OF THIS LICENCE. 
+
+This Licence applies to code to which Health Administration Corporation
+has attached the notice in Exhibit A and to related Covered Software.
+
+6. VERSIONS OF THE LICENCE. 
+
+6.1 New Versions. 
+
+Health Administration Corporation may publish revised and/or new
+versions of the Licence from time to time. Each version will be given
+a distinguishing version number.
+
+6.2 Effect of New Versions. 
+
+Once Covered Software has been published under a particular version
+of the Licence, You may always continue to use it under the terms of
+that version. You may also choose to use such Covered Software under
+the terms of any subsequent version of the Licence published by Health
+Administration Corporation. No one other than Health Administration
+Corporation has the right to modify the terms applicable to Covered
+Software created under this Licence.
+
+7. DISCLAIMER OF WARRANTY. 
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENCE ON AN "AS IS'' BASIS,
+WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS
+WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU
+(NOT HEALTH ADMINISTRATION CORPORATION, ITS LICENSORS OR AFFILIATES OR
+ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR
+OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
+OF THIS LICENCE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER
+EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION. 
+
+8.1 This Licence and the rights granted hereunder will terminate
+automatically if You fail to comply with terms herein and fail to
+cure such breach within 30 days of becoming aware of the breach. All
+sublicences to the Covered Software which are properly granted shall
+survive any termination of this Licence. Provisions which, by their
+nature, must remain in effect beyond the termination of this Licence
+shall survive.
+
+8.2 If You initiate litigation by asserting a patent infringement claim
+(excluding declatory judgment actions) against Health Administration
+Corporation or a Contributor (Health Administration Corporation
+or Contributor against whom You file such action is referred to as
+"Participant") alleging that:
+
+a) such Participant's Contributor Version directly or indirectly
+   infringes any patent, then any and all rights granted by such
+   Participant to You under Sections 2.1 and/or 2.2 of this Licence
+   shall, upon 60 days notice from Participant terminate prospectively,
+   unless if within 60 days after receipt of notice You either: (i)
+   agree in writing to pay Participant a mutually agreeable reasonable
+   royalty for Your past and future use of Modifications made by such
+   Participant, or (ii) withdraw Your litigation claim with respect to
+   the Contributor Version against such Participant. If within 60 days
+   of notice, a reasonable royalty and payment arrangement are not
+   mutually agreed upon in writing by the parties or the litigation
+   claim is not withdrawn, the rights granted by Participant to
+   You under Sections 2.1 and/or 2.2 automatically terminate at the
+   expiration of the 60 day notice period specified above.
+
+b) any software, hardware, or device, other than such Participant's
+   Contributor Version, directly or indirectly infringes any patent,
+   then any rights granted to You by such Participant under Sections
+   2.1(b) and 2.2(b) are revoked effective as of the date You first
+   made, used, sold, distributed, or had made, Modifications made by
+   that Participant.
+
+8.3 If You assert a patent infringement claim against Participant
+alleging that such Participant's Contributor Version directly or
+indirectly infringes any patent where such claim is resolved (such as by
+licence or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licences granted by such
+Participant under Sections 2.1 or 2.2 shall be taken into account in
+determining the amount or value of any payment or license.  
+
+8.4 In the event of termination under Sections 8.1 or 8.2 above, all
+end user licence agreements (excluding distributors and resellers) which
+have been validly granted by You or any distributor hereunder prior to
+termination shall survive termination.
+
+9. LIMITATION OF LIABILITY. 
+
+9.1 UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, HEALTH
+ADMINISTRATION CORPORATION, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR
+OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE
+TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
+ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE
+BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, BUT MAY ALLOW
+LIABILITY TO BE LIMITED; IN SUCH CASES, A PARTY'S, ITS EMPLOYEES',
+LICENSORS' OR AFFILIATES' LIABILITY SHALL BE LIMITED TO AUD$100. NOTHING
+CONTAINED IN THIS LICENCE SHALL PREJUDICE THE STATUTORY RIGHTS OF ANY
+PARTY DEALING AS A CONSUMER.
+
+9.2 Notwithstanding any other clause in the licence, and to the extent
+permitted by law:
+
+(a) Health Administration Corporation ("the Corporation") excludes all
+    conditions and warranties which would otherwise be implied into
+    a supply of goods or services arising out of or in relation to
+    the granting of this licence by the Corporation or any associated
+    acquisition of software to which this licence relates;
+
+(b) Where a condition or warranty is implied into such a supply and
+    that condition or warranty cannot be excluded by law that warranty
+    or condition is implied into that supply and the liability of the
+    Health Administration Corporation for a breach of that condition or
+    warranty is limited to the fullest extent permitted by law and, in
+    respect of conditions and warranties implied by the Trade Practices
+    Act (Commonwealth of Australia) 1974, is limited, to the extent
+    permitted by law, to one or more of the following at the election
+    of the Corporation:
+
+    (A) In the case of goods: (i) the replacement of the goods or the
+        supply of equivalent goods; (ii) the repair of the goods; (iii)
+        the payment of the cost of replacing the goods or of acquiring
+        equivalent goods; (iv) the payment of the cost of having the
+        goods repaired; and
+
+    (B) in the case of services: (i) the supplying of the services again;
+        or (ii) the payment of the cost of having the services supplied
+        again.
+
+10. MISCELLANEOUS. 
+
+This Licence represents the complete agreement concerning subject matter
+hereof. All rights in the Covered Software not expressly granted under
+this Licence are reserved. Nothing in this Licence shall grant You any
+rights to use any of the trademarks of Health Administration Corporation
+or any of its Affiliates, even if any of such trademarks are included
+in any part of Covered Software and/or documentation to it.
+
+This Licence is governed by the laws of the State of New South Wales,
+Australia excluding its conflict-of-law provisions. All disputes or
+litigation arising from or relating to this Agreement shall be subject
+to the jurisdiction of the Supreme Court of New South Wales. If any part
+of this Agreement is found void and unenforceable, it will not affect
+the validity of the balance of the Agreement, which shall remain valid
+and enforceable according to its terms.
+
+11. RESPONSIBILITY FOR CLAIMS. 
+
+As between Health Administration Corporation and the Contributors,
+each party is responsible for claims and damages arising, directly or
+indirectly, out of its utilisation of rights under this Licence and You
+agree to work with Health Administration Corporation and Contributors
+to distribute such responsibility on an equitable basis. Nothing herein
+is intended or shall be deemed to constitute any admission of liability.
+
+EXHIBIT A 
+
+The contents of this file are subject to the HACOS Licence Version 1.2
+(the "Licence"); you may not use this file except in compliance with
+the Licence.
+
+Software distributed under the Licence is distributed on an "AS IS"
+basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+Licence for the specific language governing rights and limitations under
+the Licence.
+
+The Original Software is "NetEpi Collection". The Initial Developer
+of the Original Software is the Health Administration Corporation,
+incorporated in the State of New South Wales, Australia.
+
+Copyright (C) 2004-2011 Health Administration Corporation, Australian
+Government Department of Health and Ageing, and others. All Rights
+Reserved.
+Contributors: See the CONTRIBUTORS file for details of contributions.
+
+APPENDIX 1. DIFFERENCES BETWEEN THE HACOS LICENCE VERSION 1.2, THE
+MOZILLA PUBLIC LICENSE VERSION 1.1 AND THE NOKIA OPEN SOURCE LICENSE
+(NOKOS LICENSE) VERSION 1.0A
+
+The HACOS Licence Version 1.2 was derived from the Mozilla Public
+License Version 1.1 using some of the changes to the Mozilla Public
+License embodied in the Nokia Open Source License (NOKOS License)
+Version 1.0a. The differences between the HACOS Licence Version 1.2
+(this document), the Mozilla Public License and the NOKOS License are
+as follows:
+
+i.    The title of the licence was changed to "Health Administration
+      Corporation Open Source Licence Version 1.2".
+
+ii.   Globally, all references to "Netscape Communications Corporation",
+      "Mozilla", "Nokia" and "Nokia Corporation" were changed to "Health
+      Administration Corporation".
+
+iii.  Globally, the words "means", "Covered Code" and "Covered Software"
+      as used in the Mozilla Public License were changed to "shall means",
+      "Covered Code" and "Covered Software" respectively, as used in
+      the NOKOS License.
+
+iv.   In Section 1 (Definitions), a definition of "Health Administration
+      Corporation" was added.
+
+v.    In Section 2, the term "intellectual property rights" used in the
+      Mozilla Public License was replaced by the term "copyrights"
+      as used in the NOKOS License.
+
+vi.   In Section 2.2 (Contributor Grant), the words "Subject to the
+      terms of this License" which appear in the NOKOS License were
+      added to the Mozilla Public License.
+
+vii.  The sentence "However, You may include an additional document
+      offering the additional rights described in Section 3.5." which
+      appears in the Mozilla Public License was omitted.
+
+viii. Section 6.3 (Derivative Works) of the Mozilla Public License,
+      which permits modifications to the Mozilla Public License,
+      was omitted.
+
+ix.   The original Section 9 (Limitation of Liability) was renumbered
+      as Section 9.1, a maximum liability of AUD$100 was specified
+      for those jurisdictions which do not allow complete exclusion of
+      liability but which do allow limitation of liability. The sentence
+      "NOTHING CONTAINED IN THE LICENSE SHALL PREJUDICE THE STATUTORY
+      RIGHTS OF ANY PARTY DEALING AS A CONSUMER.", which appears in the
+      NOKOS License but not in the Mozilla Public License, was added.
+
+x.    Section 9.2 was added in order to further limit liability to the
+      maximum extent permitted by the Commonwealth of Australia Trade
+      Practices Act 1974.
+
+xi.   Section 10 of the Mozilla Public License, which provides additional
+      conditions for United States Government End Users, was omitted.
+
+xii.  The governing law and jurisdiction for the settlement of disputes
+      in Section 11 of the Mozilla Public License and Section 10 of the
+      NOKOS License was changed to the laws of the State of New South
+      Wales and the Supreme Court of New South Wales respectively. The
+      exclusion of the application of the United Nations Convention on
+      Contracts for the International Sale of Goods which appears in
+      the Mozilla Public License was omitted.
+
+xiii. Section 13 (Multiple-Licensed Code) of the Mozilla Public License 
+      was omitted.
+
+xiv.  The provisions for alternative licensing arrangement for contributed
+      code which appear in Exhibit A of the Mozilla Public License 
+      were omitted.
+
diff --git a/LiveCD/README b/LiveCD/README
new file mode 100644
index 0000000..b7c16eb
--- /dev/null
+++ b/LiveCD/README
@@ -0,0 +1,23 @@
+=== WARNING === WARNING === WARNING === WARNING === WARNING === WARNING ===
+This directory contains a script and associated assets used in creating
+the NetEpi LiveCD on Debian or Ubuntu linux systems. These should
+only be used by expert users who have a comprehensive understanding
+of the script's functionality. Misuse *will* corrupt your system
+installation. They are not necessary for routine NetEpi use or
+installation.
+=== WARNING === WARNING === WARNING === WARNING === WARNING === WARNING ===
+
+
+
+= TIPS ========================================================================
+
+Booting resulting image:
+
+    sudo modprobe kqemu
+    qemu -m 512 -cdrom ubuntu-6.10-desktop-i386-NetEpi.iso
+
+Chroot into under-construction root:
+
+    sudo chroot netepi-live-work/ubuntu-root-new bash
+
+
diff --git a/LiveCD/README.html b/LiveCD/README.html
new file mode 100644
index 0000000..37a7a26
--- /dev/null
+++ b/LiveCD/README.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2008 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta name="Author" content="tchur at doh.health.nsw.gov.au (Tim Churches)">
+  <style type="text/css" media="all">
+   body {
+    margin: 0px;
+    color: black;
+    background-color: white;
+    font-family: "Verdana", "Arial", sans-serif;
+    font-size: 11pt;
+   }
+   h1 {
+    margin: 0px;
+    padding: 1ex;
+    background-color: #eee;
+    border-bottom: 2px solid #888;
+    text-align: center;
+   }
+   h2 {
+    border-top: 1px solid #888;
+    background-color: #eee;
+    padding-left: 1ex;
+   }
+   p {
+    margin: 1em;
+   }
+   a:link, a:visited {
+    color: black;
+   }
+    
+  </style>
+  <title>NetEpi demonstration LiveCD</title>
+ </head>
+ <body>
+ <h1>NetEpi demonstration LiveCD</h1>
+  <p>	 
+  <p>This CD contains a complete, fully functional demonstration version of NetEpi 
+  Collection, a web-based application for epidemiological and 
+  public health data collection and data management. The demonstration 
+  version, which runs under Ubuntu Linux, boots directly from the CD 
+  without needing to be installed on or oyherwise modify the hard drive of your
+  computer. The LiveCD, which is based on the
+  <a href="http://www.ubuntu.com/">Ubuntu</a>6.10 LiveCD, will boot on most
+  modern personal computers and laptops 
+  with at least 512MB of RAM (but not on Apple Mac computers at this stage).
+
+  The CD also contains movies of both NetEpi Collection and NetEpi Analysis (see below)
+  in use, as well as background and introductory documentation, and complete copies of 
+  the end user and administrator manuals. These movies and documents can also be accessed
+  directly from this demonstration LiveCD without re-booting your computer - see the links 
+  below.
+
+  <p>NetEpi Collection is a tool for securely collecting structured
+  information about cases and contacts of communicable (and other)
+  diseases through Web browsers and the Internet. New data collection
+  forms can be designed and deployed quickly by epidemiologists, using
+  a "point-and-click" interface, without the need for knowledge of or
+  training in any programming language. Data can then be collected from
+  users of the system, who can be located anywhere in Australia or the
+  world, into a centralised database. All that is needed by users of the
+  system is a relatively recent Web browser and an Internet connection
+  ("NetEpi" is short for "Network-enabled Epidemiology"). In many
+  respects, NetEpi Collection is like a Web-enabled version of the data
+  entry facilities in the very popular Epi Info suite of programmes
+  published by the US Centers for Disease Control and Prevention,
+  and in the Danish EpiData project, which is available for several
+  languages.</p>
+
+  <p>This LiveCD 
+
+  <h2>Data Persistence</h2>
+
+  <p>By default, each time this CD is booted, it starts afresh and
+  any changes that you might make are not preserved. It is, however,
+  possible to retain your changes between boots using a USB drive or local
+  hard drive. The persistance mechanism requires a specially formatted
+  file to be placed in the top-level directory of a drive. The drive can
+  be your local hard drive, although we recommend you use a USB Flash
+  drive or hard drive. Network drives are not suitable.</p>
+
+  <p>The file <b>persistence.zip</b> on this CD contains a pre-prepared
+  empty persistence file that can store up to 200MB. Unpack this file
+  to the top level directory of the drive you wish to use. Note that
+  the file must be called <b>casper-rw</b> when unpacked.</p>
+
+  <p>Once you have prepared the persistence file, you should boot the
+  LiveCD and select the second option from the boot menu: <b>Persistent
+  mode</b> (this must be selected every time you boot the LiveCD and
+  wish to enable persistence). The LiveCD should now boot normally,
+  but any changes you make will be stored to the persistence file. You
+  should see these changes next time you boot in persistence mode.</p>
+
+  <p><b>It is important that you shut down the LiveCD after use and
+  before removing the persistence drive when persistence is enabled.</b>
+  If you remove the persistence drive or turn the computer off without
+  shutting down, data may be lost. </p>
+
+  <p><b>To shut the LiveCD down, click on the red power icon that appears
+  on the top right corner of the screen.</b></p>
+
+  <h2>Movies</h2>
+
+  <p>
+  <a href="video/NetEpi_test3.html">Placeholder NetEpi Collection demo</a>
+  </p>
+ </body>
+</html>
+
diff --git a/LiveCD/config b/LiveCD/config
new file mode 100644
index 0000000..7ce2311
--- /dev/null
+++ b/LiveCD/config
@@ -0,0 +1,8 @@
+base=/usr/src/oc/health
+master_iso=${base}/data/ubuntu-7.10-desktop-i386.iso
+seed_pgdump=${base}/data/LiveCD.pgdump
+collection_src=${base}/Collection
+flash_tar=${base}/data/install_flash_player_9_linux.tar.gz
+video=${base}/data/video
+documentation=${base}/doc/NetEpi-CaseMgr-Docs
+fastcgi_url="http://www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz"
diff --git a/LiveCD/images/h5n1.jpg b/LiveCD/images/h5n1.jpg
new file mode 100644
index 0000000..cfd410a
Binary files /dev/null and b/LiveCD/images/h5n1.jpg differ
diff --git a/LiveCD/images/netepi-live-splash.README b/LiveCD/images/netepi-live-splash.README
new file mode 100644
index 0000000..043fdd9
--- /dev/null
+++ b/LiveCD/images/netepi-live-splash.README
@@ -0,0 +1,15 @@
+Splash screen image is converted to a syslinux-specific format, and
+is shown on a 640x480 VGA screen. Colours come from a pallette of 16,
+with index 0 being background colour and index 7 being foreground.
+
+Ubuntu live CD isolinux boot loader uses "isolinux/splash.rle" which is
+generated with:
+
+    pcxtoppm images/netpi-live-splash.pcx \
+        | ppmtolss16 '#ff9e00=7' '#000000=0' > isolinux/splash.rle
+
+The ppmtolss16 tool is a perl script that is included in the syslinux
+distribution, and pcxtoppm is part of the NetPBM toolchain.
+
+isolinux has been patched to use gfxboot, and this is configured to use
+isolinux/splash.pcx, so this also needs to be copied into the CD.
diff --git a/LiveCD/images/netepi-live-splash.pcx b/LiveCD/images/netepi-live-splash.pcx
new file mode 100644
index 0000000..edfbc83
Binary files /dev/null and b/LiveCD/images/netepi-live-splash.pcx differ
diff --git a/LiveCD/images/netepi-live-splash.xcf b/LiveCD/images/netepi-live-splash.xcf
new file mode 100644
index 0000000..5946bc1
Binary files /dev/null and b/LiveCD/images/netepi-live-splash.xcf differ
diff --git a/LiveCD/mk-ubuntu-livecd b/LiveCD/mk-ubuntu-livecd
new file mode 100755
index 0000000..d5486a9
--- /dev/null
+++ b/LiveCD/mk-ubuntu-livecd
@@ -0,0 +1,502 @@
+#!/bin/sh
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2008 Health Administration Corporation and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+# This script is almost certainly Linux-specific, and probably requires a
+# Debian or Ubuntu host.
+#
+# PREREQUISITES (this list is almost certainly not complete):
+#   ppmtolss16 
+#               from the package syslinux - for regenerating splash screen
+#   squashfs kernel support
+#               for unpacking the root filesystem image
+#   mksquashfs
+#               for repacking the root filesystem (Debian: squashfs-tools)
+#   mkisofs
+#               what version? Needs to be modern (boot support)
+#
+# Issues:
+# XXX better seed pgdump?
+#
+# Puzzles
+# XXX sendfile(2) is failing with EINVAL for apache
+#
+set -e
+ourname=`basename $0`
+
+function usage {
+    cat <<EOF
+Usage:"
+  ${ourname} build
+  ${ourname} cleanup
+
+NOTE - this script has only been tested in one specific context, is
+undocumented, and performs many potentially dangerous actions that can
+easily corrupt your system. Run it only if you understand exactly what
+it does, and only on a scratch installation.
+EOF
+    exit 1;
+}
+
+function cleanup {
+    echo "Cleanup..."
+#    umount -f ${root}/proc || true
+    umount -d -f ${root}
+    umount -d -f ${cdimg}
+    rm -rf ${newcdimg} ${newroot} ${downloads}
+    rmdir ${cdimg} ${root} ${scratch}
+    echo "done."
+}
+function section {
+    echo '==============================================================================='
+    echo "$*"
+}
+
+if [ "$UID" -ne 0 ] ; then echo "$0 must run as root"; exit 1; fi
+
+# Defines
+scratch=netepi-live-work
+cdimg=${scratch}/ubuntu-cd
+newcdimg=${scratch}/ubuntu-cd-new
+root=${scratch}/ubuntu-root
+newroot=${scratch}/ubuntu-root-new
+downloads=${newroot}/tmp/downloads
+assetdir=`dirname $0`
+confvars="master_iso seed_pgdump collection_src flash_tar video documentation"
+alias chroot="HOME=/root LC_ALL=C LC_MESSAGES=C chroot"
+
+case "$1" in
+    build)
+        ;;
+    cleanup)
+        cleanup ;;
+    *)
+        usage ;;
+esac
+
+. ${assetdir}/config
+for confvar in ${confvars}
+do
+    if [ ! -e "${!confvar}" ]
+    then 
+        echo "config var \"${confvar}\" points to \"${!confvar}\" but it does not exist"
+        exit 1
+    fi
+done
+
+newiso=`basename ${master_iso} .iso`-NetEpi.iso
+
+# nobble cleanup while testing...
+#trap cleanup 0
+
+# Create work directories
+mkdir -p ${scratch} ${cdimg} ${root} ${downloads}
+
+# Download extra packages
+section "Downloading extra packages"
+wget -c http://www.object-craft.com.au/projects/albatross/download/albatross-1.36.tar.gz -P ${downloads}
+wget -c http://optusnet.dl.sourceforge.net/sourceforge/pypgsql/pyPgSQL-2.5.1.tar.gz -P ${downloads}
+wget -c "${fastcgi_url}" -P ${downloads}
+
+# Mount and copy the CD image
+if [ -d ${cdimg}/isolinux -a -d ${cdimg}/casper ] ; then
+    echo "${master_iso} already mounted to ${cdimg}"
+else
+    section "Mounting ${master_iso} to ${cdimg}"
+    mount -o loop,ro ${master_iso} ${cdimg}
+fi
+
+if [ -d ${newcdimg}/isolinux ] ; then
+    echo "${newcdimg} already exists - not copying"
+else
+    section "Copying ${cdimg} to ${newcdimg}"
+    rsync -a \
+        --exclude=/bin \
+        --exclude=/programs \
+        --exclude=/casper/filesystem.squashfs \
+        ${cdimg}/ ${newcdimg}
+fi
+#(cd ${newcdimg} && sh)
+
+# extract root filesystem
+if [ -d ${root}/etc ] ; then
+    echo "root already mounted"
+else
+    section "Mounting root to ${root}"
+    mount -o loop,ro -t squashfs ${cdimg}/casper/filesystem.squashfs ${root}
+fi
+if [ -d ${newroot}/etc ] ; then
+    echo "${newroot} already exists - not copying"
+else
+    section "Copying root to ${newroot}"
+    rsync -a ${root}/ ${newroot}/
+fi
+#unsquashfs -d ${root} ${cdimg}/casper/filesystem.squashfs
+cp /etc/resolv.conf ${newroot}/etc
+#mount -o bind /proc ${newroot}/proc
+#(cd ${newroot} && sh)
+
+# now toss a bunch of stuff to make the image smaller
+#dpkg-query -W --showformat='${Installed-Size} ${Package}\n' | sort -nr | less
+section "Uninstalling stuff"
+# invoke-rc.d fails in chroot jail, so work around
+if [ ! -x ${newroot}/usr/sbin/invoke-rc.d-disabled ] ; then
+    mv ${newroot}/usr/sbin/invoke-rc.d{,-disabled}
+    cp -p ${newroot}/bin/true ${newroot}/usr/sbin/invoke-rc.d
+fi
+# hplip Packaging bug? preinit fails, so work around
+chroot ${newroot} delgroup scanner || true
+patterns='openoffice.org|language-pack|cdparanoia|linux-headers'
+matches=`chroot ${newroot} dpkg -l | egrep ${patterns} | awk '{print $2}'`
+chroot ${newroot} apt-get -y remove --purge \
+    ${matches} \
+    ttf-arphic-uming ttf-arphic-ukai ttf-baekmuk \
+    ttf-kochi-mincho ttf-kochi-gothic ttf-dejavu ttf-arabeyes \
+    ttf-bengali-fonts ttf-devanagari-fonts ttf-gujarati-fonts \
+    ttf-indic-fonts ttf-kannada-fonts ttf-lao ttf-malayalam-fonts \
+    ttf-punjabi-fonts ttf-tamil-fonts ttf-telugu-fonts ttf-thai-tlwg \
+    ttf-oriya-fonts \
+    ubiquity-frontend-gtk ubiquity ubiquity-casper ubiquity-ubuntu-artwork \
+    diveintopython gnome2-user-guide ubuntu-docs example-content \
+    ekiga gcalctool rhythmbox file-roller tsclient eog \
+    tango-icon-theme \
+    gnome-games gnome-games-data gnome-mag gnome-utils \
+    gnome-pilot gnome-pilot-conduits \
+    gnome-media-common \
+    gnome-btdownload bittorrent \
+    hplip hplip-data \
+    update-notifier alacarte aptitude tasksel tasksel-data screen \
+    apport apport-gtk python-apport-utils \
+    gthumb rss-glx xscreensaver-gl \
+    gedit gedit-common \
+    gaim gaim-data libsane xsane-common \
+    festival festlex-cmu festlex-poslex festvox-kallpc16k \
+    evolution-data-server evolution-data-server-common \
+    evolution-exchange evolution-plugins evolution-webcal \
+    nautilus-sendto evolution contact-lookup-applet bug-buddy \
+    totem-mozilla totem totem-gstreamer serpentine sound-juicer \
+    gstreamer0.10-plugins-good gstreamer0.10-x gstreamer0.10-plugins-base-apps \
+    gstreamer0.10-tools gstreamer0.10-plugins-base gstreamer0.10-gnomevfs \
+    gstreamer0.10-esd \
+    mono-runtime mono-jit mono-gac mono-common libmono0 \
+    libgcj-bc libxt-java gcj-4.1-base gcc-3.3-base \
+    libicu34 libopal-2.2.0 \
+    gimp libgimp2.0 gimp-data \
+    samba-common
+
+# Add extra bits we need
+section "Updating, Installing apache2 and postgres, dist-upgrade"
+chroot ${newroot} apt-get update
+chroot ${newroot} apt-get -y -u dist-upgrade
+chroot ${newroot} apt-get -y install apache2 apache2-mpm-prefork \
+    postgresql-8.1 postgresql-client-8.1 python-egenix-mxdatetime \
+    libc6-dev python-dev libpq-dev apache2-prefork-dev
+chroot ${newroot} apt-get clean
+
+
+# Discarding doc saves >60MB (albeit before compression)
+rm -rf ${newroot}/usr/share/doc
+
+section "Installing Albatross"
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f albatross-1.36.tar.gz && cd albatross-1.36 && python setup.py install"
+
+section "Installing pyPgSQL"
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f pyPgSQL-2.5.1.tar.gz && cd pyPgSQL-2.5.1 && python setup.py install"
+
+section "Installing mod_fastcgi"
+fastcgi_tar=`basename "${fastcgi_url}"`
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f ${fastcgi_tar} && cd mod_fastcgi-* && apxs2 -o mod_fastcgi.so -c *.c && apxs2 -i -a -n fastcgi .libs/mod_fastcgi.so"
+
+# Now remove the development environment
+# chroot ${newroot} apt-get remove --purge \
+#       libc6-dev python-dev libpq-dev
+#       apache2-prefork-dev autoconf autotools-dev libapr0-dev libdb4.3-dev
+#       libexpat1-dev libldap2-dev libpcre3-dev libpcrecpp0 libtool m4
+
+# Edit some stuff...
+# Fix PG port - postinstall script detects any PG's running on build host and
+# sets port to avoid those, but we want PG on a known port.
+sed -ie 's/^port =.*/port = 5432/' \
+    ${newroot}/etc/postgresql/8.1/main/postgresql.conf
+
+# Init script for Albatross session daemon
+if grep -q '^alsession:' ${newroot}/etc/passwd ; then true
+else
+    chroot ${newroot} groupadd -g 123 alsession
+    chroot ${newroot} useradd -u 123 -g 123 alsession
+fi
+cat > ${newroot}/etc/init.d/al-session <<"EOF"
+#!/bin/sh -e
+### BEGIN INIT INFO
+# Provides:             al-session
+# Required-Start:       
+# Required-Stop:        
+# Should-Start:         
+# Should-Stop:          
+# Default-Start:        2 3 4 5
+# Default-Stop:         S 0 1 6
+# Short-Description:    Albatross Session Daemon
+### END INIT INFO
+
+
+DAEMON=/usr/bin/al-session-daemon
+USER=alsession
+LOGFILE=/var/log/al-session-daemon.log
+PIDFILE=/var/run/albatross/session-daemon.pid
+
+test -x ${DAEMON} || exit 0
+
+umask 077
+
+case "$1" in
+    start)
+        echo "Starting albatross session daemon"
+        piddir=`dirname ${PIDFILE}`
+        touch ${LOGFILE}
+        if [ ! -d ${piddir} ]; then
+                mkdir -p ${piddir}
+        fi
+        chown ${USER} ${LOGFILE} ${piddir}
+        su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} start"
+        ;;
+
+    stop)
+        echo "Stopping albatross session daemon"
+        su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} stop"
+        ;;
+
+    *)
+        echo "Usage: $* {start|stop}"
+        exit 1
+        ;;
+esac
+EOF
+chmod a+rx ${newroot}/etc/init.d/al-session
+chroot ${newroot} update-rc.d al-session \
+    start 21 2 3 4 5 . \
+    stop 79 S 0 1 6 .
+
+# Install a script to init postgres and load our seed data
+section "Creating seed database script"
+cat > ${newroot}/etc/init.d/netepi-live <<"EOF"
+#!/bin/sh -e
+### BEGIN INIT INFO
+# Provides:             netepi
+# Required-Start:       postgresql-8.1
+# Required-Stop:        postgresql-8.1
+# Should-Start:         $syslog
+# Should-Stop:          $syslog
+# Default-Start:        2 3 4 5
+# Default-Stop:         S 0 1 6
+# Short-Description:    NetEpi Collection
+### END INIT INFO
+
+
+case "$1" in
+    start)
+        if sudo -u postgres createdb collection
+        then
+            sudo -u postgres createuser --no-superuser --no-createdb \
+                --no-createrole root || true
+            sudo -u postgres createuser --no-superuser --no-createdb \
+                --no-createrole www-data || true
+            sudo -u postgres pg_restore -d collection /var/tmp/collection.pgdump || true
+        fi
+        python /var/tmp/compile_db.py -v -u www-data ::collection: /usr/lib/cgi-bin/collection
+        ;;
+    *)
+        ;;
+esac
+EOF
+chmod a+rx ${newroot}/etc/init.d/netepi-live
+chroot ${newroot} update-rc.d netepi-live \
+    start 26 2 3 4 5 . \
+    stop 74 S 0 1 6 .
+cp ${seed_pgdump} ${newroot}/var/tmp/collection.pgdump
+cp ${collection_src}/tools/compile_db.py ${newroot}/var/tmp/
+
+# Make sure apache starts
+sed -ie 's/NO_START=1/NO_START=0/' ${newroot}/etc/default/apache2
+# Make apache start earlier, was S 91, K 91
+chroot ${newroot} update-rc.d -f apache2 remove
+chroot ${newroot} update-rc.d -f apache2 \
+    start 22 2 3 4 5 . \
+    stop 78 S 0 1 6 .
+# Make gdm (desktop) start later, was S 13, K 01
+chroot ${newroot} update-rc.d -f gdm remove
+chroot ${newroot} update-rc.d -f gdm \
+    start 23 2 3 4 5 . \
+    stop 01 S 0 1 6 .
+
+# Make sure there's a loopback interface
+echo '127.0.0.1 localhost' > ${newroot}/etc/hosts
+grep -q '^auto lo' ${newroot}/etc/network/interfaces \
+        || cat << EOF >> ${newroot}/etc/network/interfaces
+auto lo
+iface lo inet loopback
+EOF
+
+# Set up app-specific apache config
+cat > ${newroot}/etc/apache2/sites-enabled/netepi <<"EOF"
+# For some reason, the sendfile() system call fails with EINVAL within the
+# LiveCD environment - maybe unionfs doesn't support mmap, or?
+EnableSendfile off
+<IfModule mod_fastcgi.c>
+    # is this the best option?
+    FastCgiIpcDir /tmp
+    FastCgiConfig -idle-timeout 300
+    <Directory /usr/lib/cgi-bin/collection>
+        AddHandler fastcgi-script .py
+        Options +ExecCGI
+    </Directory>
+    <Directory /var/www/collection>
+        DirectoryIndex /cgi-bin/collection/menu.py
+    </Directory>
+</IfModule>
+<Directory /var/www/>
+    RedirectMatch ^/$ /collection/
+</Directory>
+EOF
+
+# Install the app
+rm -f ${newroot}/var/www/collection/help
+python ${collection_src}/install.py appname=collection dsn='::collection:' \
+    create_db=False compile_py=False install_prefix=${newroot}
+find ${newroot}/usr/lib/cgi-bin/collection -name '*.py[co]' | xargs -r rm
+chroot ${newroot} python /usr/lib/python2.4/compileall.py \
+    /usr/lib/cgi-bin/collection
+rm -rf ${newroot}/var/www/collection/help
+ln -s /cdrom/docs ${newroot}/var/www/collection/help
+rm -f ${newroot}/var/www/collection/video
+ln -s /cdrom/video ${newroot}/var/www/collection/video
+install -m 0755 -o root -g root -p ${assetdir}/images/h5n1.jpg \
+    ${newroot}/usr/share/backgrounds/
+
+# Now remove some stuff from the gnome panel config
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /apps/panel/default_setup/general/applet_id_list \
+    --type=list --list-type=string  \
+    [clock,notification_area,show_desktop_button,window_list,workspace_switcher]
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /apps/panel/default_setup/general/object_id_list \
+    --type=list --list-type=string \
+    [menu_bar,browser_launcher,session_dialog]
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --recursive-unset \
+    /apps/panel/default_setup/applets/trashapplet \
+    /apps/panel/default_setup/applets/mixer \
+    /apps/panel/default_setup/objects/email_launcher
+# and change desktop background
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /desktop/gnome/background/picture_filename \
+    --type string '/usr/share/backgrounds/h5n1.jpg'
+
+# Firefox customisation:
+# change default firefox homepage
+sed -ie 's;file:///usr/share/ubuntu-artwork/home/index.html;http://localhost/collection/;' ${newroot}/usr/share/firefox/chrome/en-US/locale/browser-region/region.properties
+# And remove BBC bookmark
+sed -ie '/BBC Headlines/d' ${newroot}/etc/firefox/profile/bookmarks.html
+# And don't warn when submitting passwd via http
+sed -i -e '/security.warn_submit_insecure/b' -e '$auser_pref("security.warn_submit_insecure", false);' ${newroot}/etc/firefox/profile/prefs.js
+# And make it start on boot
+cat > ${newroot}/usr/share/gnome/autostart/firefox.desktop <<"EOF"
+[Desktop Entry]
+Name=Firefox
+Encoding=UTF-8
+Version=1.0
+Exec=/usr/bin/firefox
+X-GNOME-Autostart-enabled=true
+EOF
+# And install flash
+tar -x -z -f ${flash_tar} -C ${downloads}
+install -m 0755 -o root -g root -p \
+    ${downloads}/install_flash_player_9_linux/libflashplayer.so \
+    ${newroot}/usr/lib/firefox/plugins/
+
+# Now clean up some temporary stuff we put in place to help the
+# chroot environment.
+rm -rf ${downloads}
+mv ${newroot}/usr/sbin/invoke-rc.d{-disabled,}
+rm ${newroot}/etc/resolv.conf
+#umount ${newroot}/proc
+
+# Now repackage it
+# Change splash screen and boot menu options
+grep -q 'Start NetEpi Collection' ${newcdimg}/isolinux/isolinux.cfg \
+    || patch -s ${newcdimg}/isolinux/isolinux.cfg <<"EOF"
+--- isolinux/isolinux.cfg-orig    2007-01-09 13:57:39.000000000 +1100
++++ isolinux/isolinux.cfg 2007-01-09 13:59:02.000000000 +1100
+@@ -3,11 +3,15 @@
+ GFXBOOT-BACKGROUND 0xB6875A
+ APPEND  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz ramdisk_size=1048576 root=/dev/ram rw quiet splash --
+ LABEL live
+-  menu label ^Start or install Ubuntu
++  menu label ^Start NetEpi Collection
+   kernel /casper/vmlinuz
+   append  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz ramdisk_size=1048576 root=/dev/ram rw quiet splash --
++LABEL livepersist
++  menu label ^Persistent mode (see README file on CD first!)
++  kernel /casper/vmlinuz
++  append  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz ramdisk_size=1048576 root=/dev/ram rw persistent quiet splash --
+ LABEL xforcevesa
+-  menu label Start Ubuntu in safe ^graphics mode
++  menu label Start NetEpi Collection in safe ^graphics mode
+   kernel /casper/vmlinuz
+   append  file=/cdrom/preseed/ubuntu.seed boot=casper xforcevesa initrd=/casper/initrd.gz ramdisk_size=1048576 root=/dev/ram rw quiet splash --
+ LABEL check
+EOF
+cp ${assetdir}/images/netepi-live-splash.pcx ${newcdimg}/isolinux/splash.pcx
+pcxtoppm ${assetdir}/images/netepi-live-splash.pcx \
+    | ppmtolss16 '#ff9e00=7' '#000000=0' > ${newcdimg}/isolinux/splash.rle
+sed -ie 's/Edgy Eft/& + NetEpi/' ${newcdimg}/README.diskdefines
+# Update manifest
+chroot ${newroot} dpkg-query -W --showformat='${Package} ${Version}\n' \
+    > ${newcdimg}/casper/filesystem.manifest
+sed -e '/ubiquity/d' ${newcdimg}/casper/filesystem.manifest \
+    > ${newcdimg}/casper/filesystem.manifest-desktop
+# Squash root
+section "Squashing ${newroot} to ${newcdimg}/casper/filesystem.squashfs"
+rm -f ${newcdimg}/casper/filesystem.squashfs
+mksquashfs ${newroot} ${newcdimg}/casper/filesystem.squashfs
+# Autorun and README
+cp ${assetdir}/README.html ${newcdimg}
+cat > ${newcdimg}/autorun.inf <<"EOF"
+[autorun]
+shellexecute=README.html
+EOF
+# Videos & doco
+rsync -a --delete ${video}/ ${newcdimg}/video/
+rsync -a --delete ${documentation}/ ${newcdimg}/docs/
+# Make example persistence file
+dd if=/dev/zero bs=1k count=200k of=${newcdimg}/casper-rw
+mke2fs -F -q -b 1024 -j -J size=4 ${newcdimg}/casper-rw
+zip -j -m ${newcdimg}/persistence.zip ${newcdimg}/casper-rw
+# Checksum
+section "md5sum"
+(cd ${newcdimg} && find . -type f -print0 | xargs -0 md5sum > md5sum.txt)
+# Master
+section "mkisofs ${newcdimg}"
+mkisofs -r -V "NetEpi LiveCD" -cache-inodes -J -l \
+    -b isolinux/isolinux.bin \
+    -c isolinux/boot.cat \
+    -no-emul-boot -boot-load-size 4 -boot-info-table \
+    -o ${newiso} ${newcdimg}
+section "All done. Thank you for your patience. Your new LiveCD is in"
+echo "${newiso}"
diff --git a/LiveCD/mk-ubuntu-livecd-7.10 b/LiveCD/mk-ubuntu-livecd-7.10
new file mode 100755
index 0000000..aaadd29
--- /dev/null
+++ b/LiveCD/mk-ubuntu-livecd-7.10
@@ -0,0 +1,535 @@
+#!/bin/sh
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2008 Health Administration Corporation and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+# This script is almost certainly Linux-specific, and probably requires a
+# Debian or Ubuntu host.
+#
+# PREREQUISITES (this list is almost certainly not complete):
+#   ppmtolss16 
+#               from the package syslinux - for regenerating splash screen
+#   squashfs kernel support
+#               for unpacking the root filesystem image
+#   mksquashfs
+#               for repacking the root filesystem (Debian: squashfs-tools)
+#   mkisofs
+#               what version? Needs to be modern (boot support)
+#
+# Issues:
+# XXX better seed pgdump?
+#
+# Puzzles
+# XXX sendfile(2) is failing with EINVAL for apache
+#
+set -e
+ourname=`basename $0`
+
+function usage {
+    cat <<EOF
+Usage:"
+  ${ourname} build
+  ${ourname} cleanup
+
+NOTE - this script has only been tested in one specific context, is
+undocumented, and performs many potentially dangerous actions that can
+easily corrupt your system. Run it only if you understand exactly what
+it does, and only on a scratch installation.
+EOF
+    exit 1;
+}
+
+function cleanup {
+    echo "Cleanup..."
+#    umount -f ${root}/proc || true
+    umount -d -f ${root}
+    umount -d -f ${cdimg}
+    rm -rf ${newcdimg} ${newroot} ${downloads}
+    rmdir ${cdimg} ${root} ${scratch}
+    echo "done."
+}
+function section {
+    echo '==============================================================================='
+    echo "$*"
+}
+
+if [ "$UID" -ne 0 ] ; then echo "$0 must run as root"; exit 1; fi
+
+# Defines
+scratch=netepi-live-work
+cdimg=${scratch}/ubuntu-cd
+newcdimg=${scratch}/ubuntu-cd-new
+root=${scratch}/ubuntu-root
+newroot=${scratch}/ubuntu-root-new
+downloads=${newroot}/tmp/downloads
+assetdir=`dirname $0`
+confvars="master_iso seed_pgdump collection_src flash_tar video documentation"
+alias chroot="HOME=/root LC_ALL=C LC_MESSAGES=C chroot"
+
+case "$1" in
+    build)
+        ;;
+    cleanup)
+        cleanup ;;
+    *)
+        usage ;;
+esac
+
+. ${assetdir}/config
+for confvar in ${confvars}
+do
+    if [ ! -e "${!confvar}" ]
+    then 
+        echo "config var \"${confvar}\" points to \"${!confvar}\" but it does not exist"
+        exit 1
+    fi
+done
+
+newiso=`basename ${master_iso} .iso`-NetEpi.iso
+
+# nobble cleanup while testing...
+#trap cleanup 0
+
+# Create work directories
+mkdir -p ${scratch} ${cdimg} ${root} ${downloads}
+
+# Download extra packages
+section "Downloading extra packages"
+wget -c http://www.object-craft.com.au/projects/albatross/download/albatross-1.36.tar.gz -P ${downloads}
+wget -c http://optusnet.dl.sourceforge.net/sourceforge/pypgsql/pyPgSQL-2.5.1.tar.gz -P ${downloads}
+wget -c "${fastcgi_url}" -P ${downloads}
+
+# Mount and copy the CD image
+if [ -d ${cdimg}/isolinux -a -d ${cdimg}/casper ] ; then
+    echo "${master_iso} already mounted to ${cdimg}"
+else
+    section "Mounting ${master_iso} to ${cdimg}"
+    mount -o loop,ro ${master_iso} ${cdimg}
+fi
+
+if [ -d ${newcdimg}/isolinux ] ; then
+    echo "${newcdimg} already exists - not copying"
+else
+    section "Copying ${cdimg} to ${newcdimg}"
+    rsync -a \
+        --exclude=/bin \
+        --exclude=/programs \
+        --exclude=/casper/filesystem.squashfs \
+        ${cdimg}/ ${newcdimg}
+fi
+#(cd ${newcdimg} && sh)
+
+# extract root filesystem
+if [ -d ${root}/etc ] ; then
+    echo "root already mounted"
+else
+    section "Mounting root to ${root}"
+    mount -o loop,ro -t squashfs ${cdimg}/casper/filesystem.squashfs ${root}
+fi
+if [ -d ${newroot}/etc ] ; then
+    echo "${newroot} already exists - not copying"
+else
+    section "Copying root to ${newroot}"
+    rsync -a ${root}/ ${newroot}/
+fi
+#unsquashfs -d ${root} ${cdimg}/casper/filesystem.squashfs
+cp /etc/resolv.conf ${newroot}/etc
+#mount -o bind /proc ${newroot}/proc
+#(cd ${newroot} && sh)
+
+# now toss a bunch of stuff to make the image smaller
+#dpkg-query -W --showformat='${Installed-Size} ${Package}\n' | sort -nr | less
+section "Uninstalling stuff"
+# invoke-rc.d fails in chroot jail, so work around
+if [ ! -x ${newroot}/usr/sbin/invoke-rc.d-disabled ] ; then
+    mv ${newroot}/usr/sbin/invoke-rc.d{,-disabled}
+    cp -p ${newroot}/bin/true ${newroot}/usr/sbin/invoke-rc.d
+fi
+# hplip Packaging bug? preinit fails, so work around
+chroot ${newroot} delgroup scanner || true
+patterns='openoffice.org|language-pack|cdparanoia|linux-headers'
+matches=`chroot ${newroot} dpkg -l | egrep ${patterns} | awk '{print $2}'`
+chroot ${newroot} apt-get -y remove --purge \
+    ${matches} \
+    ttf-indic-fonts-core ttf-arphic-uming \
+    ttf-kochi-mincho ttf-kochi-gothic ttf-arabeyes \
+    ttf-lao ttf-malayalam-fonts ttf-thai-tlwg \
+    ubiquity-frontend-gtk ubiquity ubiquity-casper ubiquity-ubuntu-artwork \
+    diveintopython ubuntu-docs example-content \
+    ekiga gcalctool rhythmbox file-roller tsclient eog \
+    gnome-games gnome-games-data gnome-mag gnome-utils \
+    gnome-user-guide gnome-pilot gnome-pilot-conduits \
+    gnome-media-common bluez-gnome \
+    gnome-btdownload bittorrent \
+    evolution-common \
+    hplip hplip-data \
+    update-notifier alacarte aptitude tasksel tasksel-data screen \
+    apport apport-gtk \
+    gthumb rss-glx xscreensaver-gl \
+    gedit gedit-common \
+    pidgin pidgin-data \
+    libsane xsane-common \
+    bogofilter-common brltty \
+    evolution-data-server evolution-data-server-common \
+    evolution-exchange evolution-plugins evolution-webcal \
+    nautilus-sendto evolution contact-lookup-applet bug-buddy \
+    totem-mozilla totem totem-gstreamer serpentine sound-juicer \
+    gstreamer0.10-plugins-good gstreamer0.10-x gstreamer0.10-plugins-base-apps \
+    gstreamer0.10-tools gstreamer0.10-plugins-base gstreamer0.10-gnomevfs \
+    gstreamer0.10-esd \
+    mono-runtime mono-jit mono-gac mono-common libmono0 \
+    libgcj-bc gcj-4.2-base \
+    libicu36 libopal-2.2 \
+    gimp libgimp2.0 gimp-data \
+    samba-common
+
+# Add extra bits we need
+section "Updating, Installing apache2 and postgres, dist-upgrade"
+chroot ${newroot} apt-get update
+chroot ${newroot} apt-get -y -u dist-upgrade
+chroot ${newroot} apt-get -y install apache2 apache2-mpm-prefork \
+    postgresql-8.2 postgresql-client-8.2 python-egenix-mxdatetime \
+    libc6-dev python-dev libpq-dev apache2-prefork-dev
+chroot ${newroot} apt-get clean
+
+
+# Discarding doc saves >60MB (albeit before compression)
+rm -rf ${newroot}/usr/share/doc
+
+section "Installing Albatross"
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f albatross-1.36.tar.gz && cd albatross-1.36 && python setup.py install"
+
+section "Installing pyPgSQL"
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f pyPgSQL-2.5.1.tar.gz && cd pyPgSQL-2.5.1 && python setup.py install"
+
+section "Installing mod_fastcgi"
+# Fool broken apxs tool
+if grep -q 'LoadModule' ${newroot}/etc/apache2/httpd.conf; then true
+else
+    echo -e "\n#LoadModule dummy_module mod_dummy.so\n" >> ${newroot}/etc/apache2/httpd.conf
+fi
+fastcgi_tar=`basename "${fastcgi_url}"`
+chroot ${newroot} sh -c "cd /tmp/downloads && tar -x -f ${fastcgi_tar} && cd mod_fastcgi-* && apxs2 -o mod_fastcgi.so -c *.c && apxs2 -i -a -n fastcgi .libs/mod_fastcgi.so"
+
+# Now remove the development environment
+# chroot ${newroot} apt-get remove --purge \
+#       libc6-dev python-dev libpq-dev
+#       apache2-prefork-dev autoconf autotools-dev libapr0-dev libdb4.3-dev
+#       libexpat1-dev libldap2-dev libpcre3-dev libpcrecpp0 libtool m4
+
+# Edit some stuff...
+# Fix PG port - postinstall script detects any PG's running on build host and
+# sets port to avoid those, but we want PG on a known port.
+sed -ie 's/^port =.*/port = 5432/' \
+    ${newroot}/etc/postgresql/8.2/main/postgresql.conf
+if grep -q '^kernel.shmmax' ${newroot}/etc/sysctl.conf; then true
+else
+    echo 'kernel.shmmax = 67108864' >> ${newroot}/etc/sysctl.conf
+fi
+
+# Init script for Albatross session daemon
+if grep -q '^alsession:' ${newroot}/etc/passwd ; then true
+else
+    chroot ${newroot} groupadd -g 123 alsession
+    chroot ${newroot} useradd -u 123 -g 123 alsession
+fi
+cat > ${newroot}/etc/init.d/al-session <<"EOF"
+#!/bin/sh -e
+### BEGIN INIT INFO
+# Provides:             al-session
+# Required-Start:       
+# Required-Stop:        
+# Should-Start:         
+# Should-Stop:          
+# Default-Start:        2 3 4 5
+# Default-Stop:         S 0 1 6
+# Short-Description:    Albatross Session Daemon
+### END INIT INFO
+
+
+DAEMON=/usr/bin/al-session-daemon
+USER=alsession
+LOGFILE=/var/log/al-session-daemon.log
+PIDFILE=/var/run/albatross/session-daemon.pid
+
+test -x ${DAEMON} || exit 0
+
+umask 077
+
+case "$1" in
+    start)
+        echo "Starting albatross session daemon"
+        piddir=`dirname ${PIDFILE}`
+        touch ${LOGFILE}
+        if [ ! -d ${piddir} ]; then
+                mkdir -p ${piddir}
+        fi
+        chown ${USER} ${LOGFILE} ${piddir}
+        su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} start"
+        ;;
+
+    stop)
+        echo "Stopping albatross session daemon"
+        su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} stop"
+        ;;
+
+    *)
+        echo "Usage: $* {start|stop}"
+        exit 1
+        ;;
+esac
+EOF
+chmod a+rx ${newroot}/etc/init.d/al-session
+chroot ${newroot} update-rc.d al-session \
+    start 21 2 3 4 5 . \
+    stop 79 S 0 1 6 .
+
+# Install a script to init postgres and load our seed data
+section "Creating seed database script"
+cat > ${newroot}/etc/init.d/netepi-live <<"EOF"
+#!/bin/sh -e
+### BEGIN INIT INFO
+# Provides:             netepi
+# Required-Start:       postgresql-8.2
+# Required-Stop:        postgresql-8.2
+# Should-Start:         $syslog
+# Should-Stop:          $syslog
+# Default-Start:        2 3 4 5
+# Default-Stop:         S 0 1 6
+# Short-Description:    NetEpi Collection
+### END INIT INFO
+
+
+case "$1" in
+    start)
+        if sudo -u postgres createdb collection
+        then
+            sudo -u postgres createuser --no-superuser --no-createdb \
+                --no-createrole root || true
+            sudo -u postgres createuser --no-superuser --no-createdb \
+                --no-createrole www-data || true
+            sudo -u postgres pg_restore -d collection /var/tmp/collection.pgdump || true
+        fi
+        python /var/tmp/compile_db.py -v -u www-data ::collection: /usr/lib/cgi-bin/collection
+        ;;
+    *)
+        ;;
+esac
+EOF
+chmod a+rx ${newroot}/etc/init.d/netepi-live
+chroot ${newroot} update-rc.d netepi-live \
+    start 26 2 3 4 5 . \
+    stop 74 S 0 1 6 .
+cp ${seed_pgdump} ${newroot}/var/tmp/collection.pgdump
+cp ${collection_src}/tools/compile_db.py ${newroot}/var/tmp/
+
+# Make sure apache starts
+sed -ie 's/NO_START=1/NO_START=0/' ${newroot}/etc/default/apache2
+# Make apache start earlier, was S 91, K 91
+chroot ${newroot} update-rc.d -f apache2 remove
+chroot ${newroot} update-rc.d -f apache2 \
+    start 22 2 3 4 5 . \
+    stop 78 S 0 1 6 .
+# Make gdm (desktop) start later, was S 13, K 01
+chroot ${newroot} update-rc.d -f gdm remove
+chroot ${newroot} update-rc.d -f gdm \
+    start 23 2 3 4 5 . \
+    stop 01 S 0 1 6 .
+
+# Make sure there's a loopback interface
+echo '127.0.0.1 localhost' > ${newroot}/etc/hosts
+grep -q '^auto lo' ${newroot}/etc/network/interfaces \
+        || cat << EOF >> ${newroot}/etc/network/interfaces
+auto lo
+iface lo inet loopback
+EOF
+
+# Set up app-specific apache config
+cat > ${newroot}/etc/apache2/sites-enabled/netepi <<"EOF"
+# For some reason, the sendfile() system call fails with EINVAL within the
+# LiveCD environment - maybe unionfs doesn't support mmap, or?
+EnableSendfile off
+<IfModule mod_fastcgi.c>
+    # is this the best option?
+    FastCgiIpcDir /tmp
+    FastCgiConfig -idle-timeout 300
+    <Directory /usr/lib/cgi-bin/collection>
+        AddHandler fastcgi-script .py
+        Options +ExecCGI
+    </Directory>
+    <Directory /var/www/collection>
+        DirectoryIndex /cgi-bin/collection/menu.py
+    </Directory>
+</IfModule>
+<Directory /var/www/>
+    RedirectMatch ^/$ /collection/
+</Directory>
+EOF
+
+# Install the app
+echo ">>> Installing NetEpi Collection"
+test -L ${newroot}/var/www/collection/help && \
+    rm ${newroot}/var/www/collection/help
+python ${collection_src}/install.py appname=collection dsn='::collection:' \
+    create_db=False compile_py=False install_prefix=${newroot}
+find ${newroot}/usr/lib/cgi-bin/collection -name '*.py[co]' | xargs -r rm
+chroot ${newroot} python -m compileall /usr/lib/cgi-bin/collection
+rm -rf ${newroot}/var/www/collection/help
+ln -s /cdrom/docs ${newroot}/var/www/collection/help
+rm -f ${newroot}/var/www/collection/video
+ln -s /cdrom/video ${newroot}/var/www/collection/video
+install -m 0755 -o root -g root -p ${assetdir}/images/h5n1.jpg \
+    ${newroot}/usr/share/backgrounds/
+
+# Now remove some stuff from the gnome panel config
+echo ">>> Tweak Gnome"
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /apps/panel/default_setup/general/applet_id_list \
+    --type=list --list-type=string  \
+    [clock,notification_area,show_desktop_button,window_list,workspace_switcher]
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /apps/panel/default_setup/general/object_id_list \
+    --type=list --list-type=string \
+    [menu_bar,browser_launcher,session_dialog]
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --recursive-unset \
+    /apps/panel/default_setup/applets/trashapplet \
+    /apps/panel/default_setup/applets/mixer \
+    /apps/panel/default_setup/objects/email_launcher
+# and change desktop background
+chroot ${newroot} gconftool-2 --direct \
+    --config-source xml:readwrite:/etc/gconf/gconf.xml.defaults/ \
+    --set /desktop/gnome/background/picture_filename \
+    --type string '/usr/share/backgrounds/h5n1.jpg'
+
+# Firefox customisation:
+echo ">>> Tweak Firefox"
+# change default firefox homepage
+# XXX
+#if [ -f ${newroot}/usr/share/firefox/chrome/en-US.jar ]; then
+#    mkdir -p ${newroot}/usr/share/firefox/chrome/en-US
+#    (cd ${newroot}/usr/share/firefox/chrome/en-US &&\
+#        unzip -o ../en-US.jar && rm ../en-US.jar)
+#    sed -ie 's;file:///usr/share/ubuntu-artwork/home/index.html;http://localhost/collection/;' ${newroot}/usr/share/firefox/chrome/en-US/locale/browser-region/region.properties
+#fi
+mkdir -p ${newroot}/usr/share/firefox/chrome/.scratch &&\
+    (cd ${newroot}/usr/share/firefox/chrome/.scratch &&\
+     jar -x < ../en-US.jar &&\
+     sed -ie 's;file:///usr/share/ubuntu-artwork/home/index.html;http://localhost/collection/;' locale/browser-region/region.properties && \
+     find -type f | jar -c@ > ../en-US.jar) &&\
+    rm -rf ${newroot}/usr/share/firefox/chrome/.scratch
+# And remove BBC bookmark
+sed -ie '/BBC Headlines/d' ${newroot}/etc/firefox/profile/bookmarks.html
+# And don't warn when submitting passwd via http
+sed -i -e '/security.warn_submit_insecure/b' -e '$auser_pref("security.warn_submit_insecure", false);' ${newroot}/etc/firefox/profile/prefs.js
+# And make it start on boot
+cat > ${newroot}/usr/share/gnome/autostart/firefox.desktop <<"EOF"
+[Desktop Entry]
+Name=Firefox
+Encoding=UTF-8
+Version=1.0
+Exec=/usr/bin/firefox
+X-GNOME-Autostart-enabled=true
+EOF
+# And install flash
+tar -x -z -f ${flash_tar} -C ${downloads}
+install -m 0755 -o root -g root -p \
+    ${downloads}/install_flash_player_9_linux/libflashplayer.so \
+    ${newroot}/usr/lib/firefox/plugins/
+
+# Now clean up some temporary stuff we put in place to help the
+# chroot environment.
+rm -rf ${downloads}
+mv ${newroot}/usr/sbin/invoke-rc.d{-disabled,}
+rm ${newroot}/etc/resolv.conf
+#umount ${newroot}/proc
+
+# Now repackage it
+echo ">>> Repackaging"
+# Change splash screen and boot menu options
+grep -q 'Start NetEpi Collection' ${newcdimg}/isolinux/isolinux.cfg \
+    || patch -s ${newcdimg}/isolinux/isolinux.cfg <<"EOF"
+1$ diff -u netepi-live-work/ubuntu-cd-new/isolinux/isolinux.cfg{.orig,}--- netepi-live-work/ubuntu-cd-new/isolinux/isolinux.cfg.orig   2007-10-16 09:53:09.000000000 +1000
++++ netepi-live-work/ubuntu-cd-new/isolinux/isolinux.cfg        2007-12-06 14:53:12.000000000 +1100
+@@ -3,21 +3,17 @@
+ GFXBOOT-BACKGROUND 0xB6875A
+ APPEND  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz quiet splash --
+ LABEL live
+-  menu label ^Start or install Ubuntu
++  menu label ^Start NetEpi Collection
+   kernel /casper/vmlinuz
+   append  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz quiet splash --
++LABEL livepersist
++  menu label ^Persistent mode (see README file on CD first!)
++  kernel /casper/vmlinuz
++  append  file=/cdrom/preseed/ubuntu.seed boot=casper initrd=/casper/initrd.gz quiet splash persistent --
+ LABEL xforcevesa
+-  menu label Start Ubuntu in safe ^graphics mode
++  menu label Start NetEpi Collection in safe ^graphics mode
+   kernel /casper/vmlinuz
+   append  file=/cdrom/preseed/ubuntu.seed boot=casper xforcevesa initrd=/casper/initrd.gz quiet splash --
+-LABEL driverupdates
+-  menu label Install with driver ^update CD
+-  kernel /casper/vmlinuz
+-  append  file=/cdrom/preseed/ubuntu.seed boot=casper debian-installer/driver-update=true initrd=/casper/initrd.gz quiet splash --
+-LABEL oem
+-  menu label ^OEM install (for manufacturers)
+-  kernel /casper/vmlinuz
+-  append  file=/cdrom/preseed/ubuntu.seed boot=casper oem-config/enable=true initrd=/casper/initrd.gz quiet splash --
+ LABEL check
+   menu label ^Check CD for defects
+   kernel /casper/vmlinuz
+EOF
+cp ${assetdir}/images/netepi-live-splash.pcx ${newcdimg}/isolinux/splash.pcx
+pcxtoppm ${assetdir}/images/netepi-live-splash.pcx \
+    | ppmtolss16 '#ff9e00=7' '#000000=0' > ${newcdimg}/isolinux/splash.rle
+sed -ie 's/Edgy Eft/& + NetEpi/' ${newcdimg}/README.diskdefines
+# Update manifest
+chroot ${newroot} dpkg-query -W --showformat='${Package} ${Version}\n' \
+    > ${newcdimg}/casper/filesystem.manifest
+sed -e '/ubiquity/d' ${newcdimg}/casper/filesystem.manifest \
+    > ${newcdimg}/casper/filesystem.manifest-desktop
+# Squash root
+section "Squashing ${newroot} to ${newcdimg}/casper/filesystem.squashfs"
+rm -f ${newcdimg}/casper/filesystem.squashfs
+mksquashfs ${newroot} ${newcdimg}/casper/filesystem.squashfs
+# Autorun and README
+cp ${assetdir}/README.html ${newcdimg}
+cat > ${newcdimg}/autorun.inf <<"EOF"
+[autorun]
+shellexecute=README.html
+EOF
+# Videos & doco
+rsync -a --delete ${video}/ ${newcdimg}/video/
+rsync -a --delete ${documentation}/ ${newcdimg}/docs/
+# Make example persistence file
+dd if=/dev/zero bs=1k count=200k of=${newcdimg}/casper-rw
+mke2fs -F -q -b 1024 -j -J size=4 ${newcdimg}/casper-rw
+zip -j -m ${newcdimg}/persistence.zip ${newcdimg}/casper-rw
+# Checksum
+section "md5sum"
+(cd ${newcdimg} && find . -type f -print0 | xargs -0 md5sum > md5sum.txt)
+# Master
+section "mkisofs ${newcdimg}"
+mkisofs -r -V "NetEpi LiveCD" -cache-inodes -J -l \
+    -b isolinux/isolinux.bin \
+    -c isolinux/boot.cat \
+    -no-emul-boot -boot-load-size 4 -boot-info-table \
+    -o ${newiso} ${newcdimg}
+section "All done. Thank you for your patience. Your new LiveCD is in"
+echo "${newiso}"
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..724631b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,33 @@
+include config.py
+include CHANGES
+include CONTRIBUTORS
+include install.py
+include liccheck.py
+include test.py
+include LGPL.txt
+include Trac_licence.txt
+include LICENCE
+include README
+include README.mac
+include README.selinux
+include Makefile
+include MANIFEST.in
+recursive-include app *
+recursive-include pages *
+recursive-include casemgr *.py README
+recursive-include cocklebur *.py Makefile
+recursive-include doc *
+recursive-include forms *
+recursive-include httpinteract *
+recursive-include images *
+recursive-include load *
+recursive-include mail *
+recursive-include Selenium *.html *.py
+recursive-include simpleinst *
+recursive-include tests *.py *.csv *.form
+recursive-include tools *
+recursive-include wiki *
+recursive-include LiveCD *
+recursive-include labsurv *
+prune simpleinst/build
+global-exclude *.pyc *.swp *.bak *,cover *~ .\#* .xvpics/* .cvsignore
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..55c7cb7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+PYTHON = python
+
+COLLECTION_CONFIG = appname=collection dsn='::collection:'
+
+.PHONY: all test liccheck sdist
+
+all: 
+	$(PYTHON) install.py $(COLLECTION_CONFIG)
+
+test:
+	(cd tests && $(PYTHON) all.py)
+
+liccheck:
+	@$(PYTHON) liccheck.py \
+		&& echo "All license banners okay" ; exit 0 \
+		|| echo "*** License banners in the above file(s) need updating" ; exit 1
+
+sdist:	liccheck
+	$(PYTHON) sdist.py
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..49bb7cf
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: NetEpi-Collection
+Version: 1.8.4
+Summary: Network-enabled tools for epidemiology and public health practice
+Home-page: http://netepi.org/
+Author: NSW Department of Health
+Author-email: Tim CHURCHES <TCHUR at doh.health.nsw.gov.au>
+License: Health Administration Corporation Open Source License Version 1.2
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/README b/README
new file mode 100644
index 0000000..cba68bb
--- /dev/null
+++ b/README
@@ -0,0 +1,1002 @@
+NetEpi Collection Version 1.8.4 README
+========================================
+
+LICENCE
+=======
+
+All materials associated with "NetEpi Collection" are Copyright (C) 2004-2010
+Health Administration Corporation (New South Wales Department of Health) and
+others (see the CONTRIBUTORS file for details).
+
+NetEpi Collection is licensed under the terms of the Health Administration
+Corporation Open Source Licence Version 1.2 (HACOS Licence V1.2), the full text
+of which can be found in the LICENCE file provided with NetEpi Collection.
+
+CHANGES TO THIS AND PREVIOUS VERSIONS OF NetEpi Collection
+==========================================================
+
+A summary of changes made in each new version appears in the CHANGES file.
+An exhaustive list of the changes made since the inception of the project
+appears in the CHANGELOG document in doc/ directory contained in this
+distribution.
+
+A NOTE FOR THOSE WISHING TO UPGRADE EARLIER VERSIONS OF NetEpi Collection
+=========================================================================
+
+Users of very early versions of NetEpi Collection - that is, versions released
+prior to May 2005 (Version 0.90) - should consult the developers via email
+for advice on how to proceed with upgrading existing installations.
+
+Users of Version 0.90 or later should carefully read the advice provided
+below (especially the INSTALLATION section). If in doubt, please do not
+hesitate to contact the developers via the netepi-discuss mailing list
+(see below) for advice on upgrading.
+
+DOCUMENTATION AND MANUALS
+=========================
+
+End-user and administrator manuals for NetEpi Collection v1.6 are still
+being updated to reflect new features and changes made since v0.90 was
+released. These updated documents will be available before the end of April
+2010 from the same Google Code and SourceForge sites from which the NetEpi
+distribution tarballs and the manuals for v0.90 are available. Some interim
+documentation of the wiki mark-up feature, which was introduced in Version
+0.94, appears in the doc directory of the distribution.
+
+
+BUG REPORTS, FEATURE REQUESTS AND GENERAL DISCUSSION
+====================================================
+
+Please report all bugs, problems, feature requests and ideas to the
+NetEpi-discuss mailing list. You need to subscribe to this list in
+order to post messages to it - see the list management Web page at:
+
+    http://lists.sourceforge.net/mailman/listinfo/netepi-discuss
+
+
+PREREQUISITES FOR USERS
+=======================
+
+Any recent Web browser (Microsoft Internet Explorer Version 6 or later,
+Mozilla or Firefox Version 3.0 or later, Opera Version 9.0 or later, all
+versions of Safari, all versions of Google Chrome), and a TCP/IP network
+connection (Internet, intranet, VPN, LAN, localhost, etc) to an instance
+of NetEpi Collection installed on a suitable Web server.
+
+Note that there is a known minor problem when Internet Explorer v5.0 is used
+to access NetEpi: if the "Intercept browser BACK button" feature is
+set to ON in the Tools | Preferences page (it is on by default), then
+clicking the browser back button results in a blank page. Clicking the 
+browser reload button fixes the problem. In the Safari browser, 
+if the "Intercept browser BACK button" feature is set to ON, then 
+the browser back button is effectively disabled. If it is set to OFF
+or if Javascript is disabled in the Safari, Konqueror and Opera browsers,
+use of the browser back button may result in unexpected behaviour, which 
+can be avoided by simply using the "<<Back" buttons provided in the NetEpi
+application itself.
+
+In summary, with default settings, the browser back button behaves in an
+intuitive manner when using the NetEpi application in all web browsers
+except Safari. In Safari, the browser back button is effectively disabled
+and users need to use the "<<Back" navigation buttons provided in the
+application itself (which is preferable anyway).
+
+
+NOTES FOR APPLE MAC OS X USERS
+==============================
+
+Please see the accompanying README.mac file for some notes specific to 
+installation on Apple Mac OS X computers.
+
+
+PREREQUISITES FOR SERVER INSTALLATION
+=====================================
+
+0)  A recent Linux, Unix or Apple OS X server (either a dedicated server,
+    which is preferred, or a workstation acting as a server). At this time,
+    running the application under Microsoft Windows is NOT supported.
+
+1)  Apache 2.x Web server or an equivalent HTTP server (needs to be able
+    to run CGI). The web server needs to be secured in the usual fashion
+    if it is to be accessible via the Internet.
+
+2)  Python 2.4 or later. If using Python 2.4, then version 2.4.1
+    or later is required because earlier versions of Python 2.4 have
+    a defect in their regular expression library (bug #1088891), which
+    adversely effects Albatross web template parsing.
+
+    If possible, you should use the python packaged with your server
+    platform (Python comes pre-installed in RedHat, Fedora Core, Debian,
+    Ubuntu and Mac OS).  
+    
+    Alternatively, you can download the Python source and build that:
+
+    Python can be obtained from:
+
+        http://www.python.org/ 
+
+    If you prefer, you can compiled Python from source:
+
+        1) Unpack the Python tar archive (.tgz file) 
+
+        2) As root in the top level directory created by the tar archive: 
+
+            ./configure 
+
+        3) make 
+
+        4) make test 
+
+        5) make install 
+
+        6) check that the python executable is in the default path
+
+3)  Albatross 1.35 or later (Web application framework for Python)
+
+    NOTE: If using Python 2.6, Albatross 1.40 or later is required.
+
+    Albatross may be downloaded from:
+
+        http://www.object-craft.com.au/projects/albatross/ 
+
+        1) Unpack the Albatross tar archive 
+
+        2) As root in the top level directory created by the tar archive: 
+
+            python setup.py install 
+
+        3) The Albatross session server needs to be started in the
+           security context of a non-privileged user (not root or
+           nobody!) - typically you want to start it automatically from
+           an init script which contains a line such as:
+
+            su -s /bin/sh foo -c "\
+                /usr/local/bin/al-session-daemon \
+                    --pidfile=/home/foo/al-session-daemon.pid \
+                    --log=/home/foo/al-session-daemon.log \
+                    start > /dev/null 2>&1" < /dev/null
+
+           where "foo" is the name of the user context in which the
+           session server daemon will run.
+
+4)  eGenix mx tools (specifically mx.DateTime)
+
+    If you are using RedHat or Fedora Core, install the "mx" package via:
+
+        sudo yum install mx
+
+    If you are using Debian or Ubuntu, install "python-egenix-mxdatetime" via:
+
+        sudo apt-get install python-egenix-mxdatetime
+
+    Alternatively, download and install from source:
+
+        http://www.egenix.com/files/python/eGenix-mx-Extensions.html 
+
+        1) Unpack the tar archive 
+
+        2) As root in the top level directory created by the tar archive: 
+
+            python setup.py install
+
+5)  PostgreSQL, minimum 8.1.x, 8.3 or later recommended. We strongly recommend
+    the use of and/or upgrading to the latest available point release
+    (as designated by the third digit in the version number) for a given
+    release version (as designated by the first two version number digits)
+    of PostgreSQL, as later point releases contain important security fixes.
+
+    If your operating system vendor supports PostgreSQL, we recommend using
+    their packages (using yum or apt-get). Make sure that you install packages
+    for the PostgreSQL server, the PostgreSQL client libraries and the
+    PostgreSQL developer libraries (the latter are needed to install pyPgSQL
+    or ocpgdb - see below).  Alternatively, PostgreSQL may be downloaded from:
+
+        http://www.postgresql.org/
+
+        Compile PostgreSQL with defaults. 
+
+        Make sure your PostgreSQL installation has a "root" user that can
+        create new databases and users (issue the command "createuser -a
+        -d root" if needed).
+
+        You will probably also need to edit the PostgreSQL pg_hba.conf 
+	configuration file to allow the user in whose context the web 
+	server (httpd) runs access to the database. For Debian-based systems,
+	something like this may need to be added:
+	
+	    local all www-data sameuser;
+	   
+	Or for Red Hat/Fedora Core-based systems:
+
+	    local all apache sameuser;
+	
+
+6)  A Python-to-PostgreSQL interface module:
+
+ EITHER pyPgSQL Version 2.5.1 or later
+
+    pyPgSQL may be downloaded from:
+
+        http://sourceforge.net/projects/pypgsql/
+
+        1) Unpack the tar archive 
+
+        2) Change into the directory created by the tar archive
+
+        3) As root install the package:
+
+            python setup.py install
+
+ OR ocpgdb
+
+    ocpgdb may be downloaded from:
+
+        http://code.google.com/p/ocpgdb/downloads/list
+
+        1) Unpack the tar archive
+
+        2) Change into the directory created by the tar archive
+
+        3) As root install the package:
+
+            python setup.py install
+
+ (Disclaimer: Andrew McNamara, the principal NetEpi developer, wrote
+  the ocpgdb adaptor module, and naturally we think it is the better
+  of the two adaptor modules listed above.)
+
+7) [optional but strongly recommended] FastCGI
+
+    NetEpi Collection can be deployed using mod_fastcgi for Apache. This
+    provides a very significant improvement in the responsiveness of
+    the application and is recommended for high-volume installations
+    or those with many users. However, FastCGI can be a bit tricky to
+    install (although should not be too hard for most sysadmins).
+
+    Detailed instructions for compiling and installing mod_fastcgi
+    (FastCGI) are beyond the scope of this document - please see the
+    FastCGI Web site at http://www.fastcgi.com for details.
+
+    Once installed, you will need to add some directives to your Apache
+    configuration file. The following are EXAMPLES ONLY of directives,
+    in this case for Apache2 on Fedora Core Linux - you may need slightly
+    different directives for your version of Apache on your server -
+    consult the FastCGI and/or Apache documentation.
+
+    # Dynamic Shared Object (DSO) Support 
+    LoadModule fastcgi_module modules/mod_fastcgi.so
+
+    ScriptAlias /cgi-bin/collection/ "/var/www/cgi-bin/collection/"
+
+    # AddModule mod_fastcgi.c
+    <IfModule mod_fastcgi.c>
+        # is this the best option?
+        FastCgiIpcDir /tmp 
+        FastCgiConfig -idle-timeout 300
+        <Directory /var/www/cgi-bin/collection>
+            AddHandler fastcgi-script .py
+            Options +ExecCGI
+        </Directory>
+    </IfModule>
+
+    Finally, if you run Collection under FastCGI, remember that you need to
+    stop and then restart Apache if you upgrade the Collection application or
+    HTML templates. FastCGI also maintains persistent database connections
+    (one of the reasons it is so much faster than plain CGI), so you
+    will need to stop Apache if you wish to upgrade your database etc.
+
+8)  [optional] MatPlotLib v0.90.x or later
+
+    MatPlotLib may be downloaded from:
+       
+        http://matplotlib.sourceforge.net
+
+    Note that MatPlotLib has several dependencies, and for recent Linux
+    distributions, by far the easiest way to install it is to use a package,
+    which will (or should) automatically resolve the dependencies and install
+    any additional libraries required.  If you choose to build and install
+    MatPlotLib yourself from source please read the MatPlotLib installation
+    notes carefully to ensure that its dependencies on various third-party
+    libraries have been met first (this is likely to include the "dev"
+    or "devel" versions of the cairo, freetype, pango, libpng and zlib
+    libraries).
+    
+    Debian and Ubuntu users can install matplotlib via:
+
+        sudo apt-get install python-matplotlib
+
+    RedHat/CentOS/Fedora Core users can find RPM packages for matplotlib
+    and its dependencies via RPMFind:
+
+        http://www.rpmfind.net/linux/rpm2html/search.php?query=python-matplotlib
+
+    NetEpi Collection will still function perfectly well if you do not
+    install MatPlotLib on your web server, however the epi curve graphs
+    will not be available.
+
+9)  [optional] Graphviz - Graph Visualization Software
+
+    GraphViz source may be downloaded from:
+
+        http://www.graphviz.org/
+
+    For Debian and Ubuntu systems, install via:
+
+        sudo apt-get install graphviz
+
+    RedHat/CentOS/Fedora Core, you will probably need to use the graphviz,
+    graphviz-gd, gtkglext-libs and gts packages from here:
+
+        http://www.graphviz.org/Download_linux_rhel.php
+
+    NetEpi Collection will still function if you do not install GraphViz
+    on your web server, however the case-contact relationship visualisation
+    report graphs will not be available.
+
+10)  [very optional]  Selenium Core 0.8.0 or later
+
+        A work-in-progress Selenium end-to-end test suite is also
+        included. The Selenium tests are useful for verifying the 
+        correct operation of a NetEpi Collection installation, 
+        although their main purpose is to act as a set of regression 
+        tests for NetEpi Collection developers.
+
+        Please see the README file in the Selenium tests directory for 
+        further information.
+
+        To run these tests, you will need the Selenium Core web testing
+        framework, available from:
+
+        http://www.openqa.org/selenium/
+
+        1) Unpack the Selenium distribution, and recursively copy the
+           "selenium" sub-directory to your web server document directory
+           (typically /var/www or /var/www/html).
+
+        2) Copy the contents of the Collection "Selenium" directory to the
+           "tests" sub-directory of the selenium directory on your
+           web server.
+
+        3) Point your browser at http://webserver/selenium/TestRunner.html,
+           where "webserver" is the name of your web server.
+
+        4) Select a test suite, then run the selected test.
+
+        The tests assume a fresh, default Collection installation with a
+        newly initialised database, and with admin user password sn00Py
+        (double zero, not oh oh).
+
+        ***NOTE: please remember to reset the admin password after
+                 you have finished testing!
+
+        The Selenium web testing framework is written in Javascript and
+        runs within your browser. Most modern browsers will work: IE,
+        Firefox and Safari on all supported platforms. Opera doesn't
+        appear to work at this time.
+
+        We strongly recommend that you remove the selenium directory
+        from any production Web server after testing is complete,
+        as it is unknown whether Selenium presents any security holes
+        (probably not, but Selenium is a complex piece of code and is
+        still itself a work-in-progress).
+
+11)  [highly optional] ClientForm & ClientCookie
+
+    A load testing framework is also included, however the tests are currently
+    broken due to a comprehensive redesign of the application interface.  It is
+    included in this distribution as a placeholder, and will be updated in a
+    future release. Please see the README in the load directory for further
+    information. A contribution of fixed or extended load tests is welcome.
+
+    This framework requires:
+
+        ClientForm
+
+            http://wwwsearch.sourceforge.net/ClientForm/
+
+            1) Unpack the tar archive
+
+            2) As root in the top level directory created by the tar archive: 
+
+                python setup.py install
+
+        ClientCookie
+
+            http://wwwsearch.sourceforge.net/ClientCookie/
+
+            1) Unpack the tar archive
+
+            2) As root in the top level directory created by the tar archive: 
+
+                python setup.py install
+
+12) [completely optional] Documentation diagrams are prepared with Dia (0.97):
+
+        http://www.gnome.org/projects/dia/
+
+        (only needed if you wish to modify the diagrams)
+
+13) [entirely optional] Icons, logos and "image" buttons are prepared with 
+    The GIMP:
+
+        http://www.gimp.org/
+
+        (only needed if you wish to modify logos and icons).
+
+
+
+INSTALLATION of NetEpi Collection
+=================================
+
+    Make sure your PostgreSQL installation has a "root" user that can
+    create new databases and users (use "createuser root" if needed).
+
+    To perform a default NetEpi install, simply run "make" in the top
+    level directory as root. The install scripts know default paths and
+    web user names for:
+
+        RedHat Enterprise Linux/Fedora Core Linux
+        Debian Linux (and most derivatives)
+        Apple OS X
+
+    Additional platforms can be added by editing simpleinst/platform.py
+    (If you add entries for other platforms, please let us know so we can
+     include support for those platforms in future releases.)
+
+    The installer will ask for an initial password for the "admin"
+    user. You can change this password from the admin web interface,
+    but this initial password will be required to log in to the admin
+    application.
+
+    A number of parameters can be configured at install time. You can
+    either edit the Makefile and change the "COLLECTION_CONFIG" option,
+    or pass them on the command line:
+
+        make COLLECTION_CONFIG="\
+            appname=foo \
+            dsn='::foo:' \
+            registration_notify='nobody at example.com' \
+            exception_notify='nobody at example.com'"
+
+    After installation, configuration parameters can be changed by
+    editing the "config.py" file in the "collection" cgi-bin directory.
+
+    Configurable parameters include:
+
+        appname (default: "collection")
+            Application name - effects install paths, cookies, etc. The
+            application name must be a valid file name, and must not
+            contain path components (path separators or drive letters).
+
+        apptitle (default: "NetEpi Collection")
+            User visible application name
+
+        cache_form_summaries (default: False)
+            If True, form summaries are only generated when a form is
+            updated. If False, they are generated on demand (which requires
+            loading the form definition and form instances for each summary
+            generated). The main drawback of caching summaries is dates are
+            formatted according to the editing user style preferences, rather
+            than the viewing user's style preference (which can be particularly
+            confusing when one uses date style DMY and the other uses MDY).
+
+        cgi_dir (default is platform dependent)
+            Application scripts and data will be placed into a sub-directory
+            "appname" off this directory.  Default depends on operating
+            system, but is typically something like /usr/lib/cgi-bin or
+            /var/www/cgi-bin.
+
+        compile_py (default: True)
+            If True, the installer will byte-compile all python code after
+            installing it.
+
+        contact_label (default: Association)
+            Controls the presentation name of associations between cases
+            (formerly "contacts"). Note that the application makes a
+            plural form of the label by appending an 's'.
+
+        create_db (default: True)
+            If True, the installer will run the database conditioning tool
+            - if the database already exists, the scheme is upgraded if
+            necessary. If the database does not exist, it is created.
+
+        debug (default: False)
+            If True, enables the display of diagnostic information at
+            the foot of the application page.
+
+        date_style (default: DMY)
+            Determines the default date style (for input and output)
+            for an instance, although it can still be overridden by
+            individual users. "DMY" means "DD/MM/YYYY" format, "MDY"
+            means "MM/DD/YYYY" format and "ISO" means "YYYY-MM-DD" format.
+
+        dsn (default: "::collection:")
+            Data Source Name - describes database host, port, database
+            name, database user and database password. Currently database
+            passwords are not supported - this will change once we have
+            audited where the DSN is exposed.
+
+        dup_per_syndrome (default: False)
+            If set to True, person (duplicate) searching is restricted
+            to the current syndrome, if False, persons associated with
+            any syndrome will be returned.
+
+        enable_matplotlib (default: False)
+            If matplotlib is available, setting this to True will
+            enable additional visualisations (only report epi curve at
+            this time).
+
+        exception_notify (no default - no e-mail notifications)
+            Application exceptions will be e-mailed to this address,
+            if set.  Multiple addresses can be listed, comma separated.
+
+        form_rollforward (default: True)
+            If False, existing forms remain associated with the version
+            of form they were created under.
+
+            If True, the form editor attempts to upgrade data collected
+            by older versions of the form when a new version of the form
+            is deployed. This can fail if the new schema is incompatible
+            with the old (this prevents form deployment, but does not
+            alter the form data).
+
+        group_label (default: Context)
+            Controls the presentation labelling of the "group" entity.
+
+        helpdesk_contact (default: "the NetEpi Helpdesk")
+            Contains a description of the body responsible for supporting
+            the application. Used in the context "please contact ...". 
+            The string can contain HTML elements (for example, a URL or
+            e-mail link).
+
+        html_dir (default is platform dependent)
+            Fixed application content (images, help text, style sheets)
+            will be installed in an "appname" sub-directory of this
+            directory.
+
+        immediate_create (default: True)
+            If True, cases are created when the user clicks the "Create"
+            button on the search result pages.
+
+            If False, the user must explicitly create the case from
+            the case page. This allows the user to review all personal
+            details prior to creating the record, rather than just the
+            summary information shown in the search results.
+
+        install_debug (default: False)
+            Enables debugging output from the installer
+
+        install_logo
+        install_logo_small
+            If set, these specify paths to alternate application logos that
+            will be installed with the application. The first is the login page
+            logo, the second is the logo that appears in every page banner.
+
+        install_verbose (default: False)
+            Enables verbose output from the installer
+
+        login_helpdesk_contact (default: "the NetEpi Helpdesk")
+            Similar to helpdesk_contact, but shown in less secure contexts
+            (login and new user registration) where the disclosure of internal
+            address and phone numbers could be problematic. Contains
+            a description of the body responsible for supporting the
+            application. Used in the context "please contact ...".  The string
+            can contain HTML elements (for example, a URL or e-mail link).
+
+        max_requests (default: 1000)
+            If non-zero, after servicing this many requests, we exit gracefully
+            to minimise the impact of memory fragmentation and object leaks
+            (only relevant for persistent application servers).
+
+        nobble_back_button (default: True)
+            When True, this enables a mechanism that exploits side-effects
+            of the <iframe> tag to intercept the browser <back> button and
+            thus reduce the change of the browser and application getting
+            out of sync.
+
+        notification_host (default: local)
+        notification_port (default: 13535)
+            The optional notification daemon makes certain data changes
+            propagate between application server instances faster. Application
+            processes send the notification daemon messages to announce
+            that a certain object has changed, and all interested clients
+            then receive a copy of this notification, and discard any cached
+            copies of the referenced object.
+
+            notification_host can be set to "none", "local", or an ip address
+            or host name. When set to "none", no notification daemon will be
+            started and time-based cache expiry is used. If set to "local", a
+            daemon local to this instance will be automatically started if one
+            is not already running, with communications occurring over unix
+            domain sockets.  If set to a hostname or ip address, application
+            processes will attempt to connect to that address and no local
+            daemon will be started (use the stand-alone daemon on the server
+            host).
+        
+            When the notification daemon is not local, notification_port sets
+            the port number to use.
+
+            NOTE - the notification daemon protocol is currently
+            unauthenticated - anyone who can connect to the port can generate
+            spurious notification messages, or kill the daemon. By default,
+            the daemon only accepts connections from the local machine. In
+            environments with untrusted users, the notification daemon should
+            not be used at this time (set port to a null string: '').
+
+            NOTE - you must have ONE notification daemon per NetEpi
+            Collection INSTANCE.
+
+        order_syndromes_by (syndrome_id DESC)
+            An SQL "order by" clause used for sorting the presentation
+            of syndromes. Recommended columns are "syndrome_id"
+            (essentially the order in which the syndromes were added),
+            "name" or "post_date". This must be a valid column name from
+            the syndrome_types table. "DESC" can be appended to reverse
+            the sort order.
+
+        person_label (default: Person)
+            Controls the presentation labelling of the "person" entity.
+
+        registration_notify (no default - no e-mail notifications)
+            New user registrations and locked-account notifications will
+            be e-mailed to this address, if set. Multiple addresses can
+            be listed, comma separated.
+
+        session_secret (default: random)
+            This string is mixed with any session data that makes a
+            round trip via the user's browser to prevent unauthorised
+            modifications being made. The string must be kept secret,
+            and should not be shared with other applications.
+
+        session_server (default: "localhost:34343")
+            If set, contains the host and port of the Albatross session
+            server. Server-side sessions will be used. Consult the
+            Albatross documentation for more information on the session
+            server.
+
+            If not set, hidden-field sessions are used - session data
+            will be saved in hidden form fields, and protected from
+            alteration by a cryptographic hash (but are NOT encrypted).
+
+        session_timeout (default: 1200)
+            When using server-side sessions, sessions will expire after
+            this many seconds (both the session server data, and the
+            browser cookie are based on this setting).
+
+        show_all_syndromes (default: False)
+            If True, show all syndromes on main page, not just those the
+            user has access to.
+            
+        subbanner (default: "Network-enabled tools for epidemiology 
+                             and public health practice.")
+            Typically rendered in a smaller font under the apptitle on login
+            and page banner screens.
+
+        syndrome_label (default: "Case Definition")
+            Controls the presentation name of "syndromes". Typical values
+            are "Case Definition", "Syndrome", or "Outbreak ID".
+
+        tabbed_demogfields_threshold (default: 0)
+            When many demographic fields are enabled, the screen can become
+            quite cluttered. If the count of enabled fields exceeds the
+            this threshold, the application switches to a tabbed rendering
+            of the demographic fields, where fields are grouped together by
+            function, and the groups then rendered inside tabs. To disable
+            the tabbed rendering, set to 999.
+
+        tracedb (default: False)
+            If set to True, application SQL queries will be written to
+            the web server error log. This is primarily a developer tool.
+
+        unit_label (default: Role)
+            Controls the presentation labelling of the "unit" entity.
+
+        user_browser (default: True)
+            If True, allow users to view details of other users of the
+            system (subject to that user's privacy settings).
+
+        user_check_interval (default: 30 days)
+            User details check interval in days - if the user has not updated
+            their details in this time, remind them to review them. Set it
+            to 0 to disable this check.
+
+        user_registration_mode (default: 'register')
+            New user registration mode. Choices are:
+
+            none        Users can only be added by admins.
+
+            register    A button on the login page invites the user to
+                        register their details. After registering, their
+                        details are reviewed by an admin prior to their account
+                        being enabled.
+
+            invite      An existing user invites a prospective user onto the
+                        system via a one-time URL that takes the prospective
+                        user to a registration screen.  After registering,
+                        their details are reviewed by an admin prior to their
+                        account being enabled.
+
+            sponsor     An existing user sponsors a prospective user onto the
+                        system via a one-time URL that takes the prospective
+                        user to a registration screen. After registering, the
+                        sponsoring user verifies the identity of the new user
+                        and then enables their access.
+
+        web_user (default is platform dependent)
+            User name to install files as - this should match the user
+            id your web server runs CGI scripts as.
+
+    The installation process creates a minimally populated database. After
+    installation, the web admin interface should be used to create
+    additional units, users and groups of units.
+
+    Note that it is safe to run the installer against an already installed
+    instance of NetEpi Collection v0.90 (released in May 2005) or later,
+    although it is STRONGLY recommended that a backup be performed first:
+    a backup of the relevant Web cgi-bin and html directories as well as a
+    verified "dump" of the database using one of the PostgreSQL dump tools
+    (see the PostgreSQL documentation for details).
+
+    The application should be shut down before running the installer
+    (typically by stopping the web server). The installer will update any
+    application libraries that have changed, rewrite the configuration,
+    upgrade the schema if necessary, and attempt to correct any problems
+    in form registration.  Existing data is generally left alone, except
+    in the case where the installer detects an out of date schema.
+
+    Direct access to the data collected is best done via an ODBC
+    connection, using the PostgreSQL ODBC drivers available from the
+    PostgreSQL Web site. A database user which has only read-only access
+    to the database should probably be used for most ODBC connections.
+
+
+ACCESSING YOUR INSTALLATION of NetEpi Collection
+================================================
+
+If running in FastCGI mode, you should restart the apache daemon (httpd) 
+on your Web server, and access the user and admin apps at URLs similar to
+(substitute the address of your Web server for "www.yourserver.org"):
+
+    http://www.yourserver.org/cgi-bin/collection/app.py
+
+If you are accessing NetEpi across the Internet, you should use SSL by
+substituting https in the above URLs and configuring your web server to
+support SSL. We recommend that you configure Apache to require SSL for all
+interactions with NetEpi.
+
+Note that if the default 'register' mode is used for the
+user_registration_mode configuration option (see above), then the login page
+of NetEpi Collection allows potential users to apply for a user account.
+This new user application process is, by necessity, unauthenticated, and is
+therefore potentially vulnerable to abuse by third parties who may waste
+time and resources by submitting false account applications. To protect
+against this, it is strongly recommended that if NetEpi is deployed on the
+Internet, and if 'register' mode is used, then an additional layer of http
+basic authentication is employed using a single username/password pair that
+can be shared between all legitimate users (and emailed to new users to allow
+them to apply for their accounts). Note that this is merely an additional
+"first-order" layer of protection as an adjunct to per-user login names and
+passwords, which should NOT be shared between users or emailed around. x.509
+certificates installed in client browsers could also be used for this purpose.
+
+The 'invite' and 'sponsor' modes for the user_registration_mode option
+were introduced to avoid the need for such additional layers of password
+protection and to reduce the likelihood of new account "phishing"
+attempts by imposters.
+
+The following directives can be added to your Apache configuration to
+provide a more convenient URL to access the applications (the directory
+paths may need to be changed to suit your configuration):
+
+    <Directory /var/www/html/collection/>
+        DirectoryIndex /cgi-bin/collection/menu.py
+    </Directory>
+
+This directive will result in a menu page being shown on access to
+
+    http://www.yourserver.org/collection/
+
+
+BACKUPS
+=======
+
+    The system administrator needs to consider how backups of the
+    database are to be performed - we suggest use of the PostgreSQL
+    pg_dump command to dump the database to a file which will then be
+    backed up as you would normally back up files.  There is no need to
+    shut down the database to do back-ups, provided that you use pg_dump,
+    so these backups can be scheduled as cron jobs and the dump file
+    copied to another machine etc. Ability to restore from the pg_dump
+    files should be tested, regularly, of course.
+
+    As of version 0.93 of NetEpi Collection, it is no longer necessary to 
+    additionally backup the form definitions, as these are now stored in
+    the database itself. However, it may be wise to store backups of 
+    XML exports of the form definitions from time-to-time. These can be
+    obtained via the form editor in the admin application.
+
+    We do not recommend backing up the PostgreSQL data-files directly,
+    as the format of these changes between PostgreSQL versions, and the
+    database must be shut down for the duration of the dump to get a
+    consistent representation of the database.
+
+    After restoring a database dump (with pg_restore), the installer 
+    should be run again to ensure that the forms are correctly registered,
+    and that the application schema is up to date.
+
+
+Stand-alone Notification Daemon
+===============================
+
+When the application is being served by multiple machines, a single
+notification daemon should be shared by all the servers. Set the
+notification_host configuration option to the name or IP address of the
+server that runs the daemon, and start the daemon explicitly:
+
+    casemgr/notification/daemon --listen <ip_addr> --port <port> --bg
+
+NOTE - the notification daemon protocol is currently unauthenticated - anyone
+who can connect to the port can generate spurious notification messages,
+or kill the daemon. By default, the daemon only accepts connections from the
+local machine. In environments with untrusted users, the notification daemon
+should not be used in stand-alone mode at this time (set notification_port
+to a null string: '' or leave notification_host set to the default 'local').
+
+Client interactivity reporting
+==============================
+
+The application uses some client-side JavaScript to collection data on
+the "interactivity" of the application - specifically, the time taken
+from the submission of a request to the resulting page "on load" event
+firing. This includes a round trip across the network, the application
+run time, and the time taken for the browser to render the page. The
+application run time is also recorded separately. This data is written
+to the web error long on the NEXT interaction with the server. The client
+reports look like:
+
+    client report: start=1184734979 ip=127.0.0.1 server=0.056 client=0.411 page=tools
+
+A tool, tools/client_report.py is supplied to extract these lines from
+the error log and either write them to a CSV file, load them into a
+NetEpi Analysis dataset, or plot them via MatPlotLib. For example,
+to extract the reports in CSV format to stdout:
+
+    python tools/client_report.py --csv /var/log/apache2/error.log*
+
+To load the reports into a NetEpi Analysis dataset:
+
+    python tools/client_report.py \
+        --dsname=clientreports \
+        --dslabel='NetEpi Collection client reports' \
+        --dspath=SOOM_objects -v \
+        /var/log/apache2/error.log*
+
+To plot the reports, starting from the beginning of 2008, grouped by
+application page, for the "collection" instance:
+
+    python tools/client_report.py \
+        --plot --start 2008-1-1 --groupby page --filter inst=collection \
+        /var/log/apache2/error.log*
+
+For more options, see the usage message:
+
+    python tools/client_report.py --help
+
+
+seed_db
+=======
+
+    NOTE: This tool has not been kept up to date, and it no longer works
+          with current versions of NetEpi Collection.
+
+    For test (NOT production) installations the tools/seed/seed_db script
+    can be used to install some sample users and units. Note that the
+    sample users all have random passwords - before they can be used,
+    the administrator will need to set their password to a known string. 
+
+    To load the sample data, as root (or the DBA user):
+
+        sh tools/seed/seed_db <database_name>
+
+    NOTE: The installer must now be run again to fully register the forms
+    and case definitions added by seed_db (see the Installation section
+    above). If you omit this step, application exceptions will occur when
+    the user attempts to fill in a form - running the installer will fix this.
+
+===============================================================================
+
+Directories:
+
+    app                 application & fixed components (online help, images)
+    pages               page templates and presentation logic
+    casemgr             application "business" logic
+    cocklebur           db and form abstractions
+    config.py           application site configuration
+    doc                 system manager and developer documentation
+    forms               example forms
+    labsurv             weekly respiratory virus laboratory surveillance app
+    httpinteract        Javascript tool to collect client network statistics
+    images              logo xcf (gimp) files
+    install.py          installer rules
+    load                load testing tools
+    mail                e-mail templates
+    Selenium            Selenium-based web test
+    simpleinst          installer libraries
+    tests               unit tests
+    tools               miscellaneous installation and other tools
+        seed                database seed files
+    wiki                files from Trac to support wiki mark-up of text
+    
+Additionally, the LiveCD directory contains a script and associated
+assets used in creating the NetEpi LiveCD on Debian or Ubuntu
+Linux systems. These should only be used by expert users who have a
+comprehensive understanding of the script's functionality. Misuse *will*
+corrupt your system installation. They are not necessary for routine
+NetEpi use or installation.
+
+===============================================================================
+
+e-mail templates:
+
+The "mail" directory contains the default templates for notification
+e-mails generated by the system, formated as RFC822 messages. It is
+possible (but not recommended) to change these templates for specific
+circumstances, although care must be taken to maintain the correct format.
+
+Variables available for substitution into the templates are formated as
+{{variable_name}}, and vary from template to template but all include:
+
+    Configuration variables
+    {{time}}
+    {{datetime}}
+    {{date}}
+
+Where the message concerns a user, additional variables are available:
+
+    {{username}}
+    {{fullname}}
+    {{title}}
+    {{agency}}
+    {{phone_work}}
+    {{phone_mobile}}
+    {{email}}.
+
+The templates are:
+
+    exception_notify
+
+        Sent to the configured exception_notify address when an error
+        occurs in the application logic. Additional variables include
+        the details of the {{exception}}.
+
+    register_invite
+
+        Sent to a prospective user when an user invites them to register
+        to use the system. Additional variables include details of the
+        registering user, a URL that takes the user to the registration
+        page {{url}}, and the {{enable_key}}. In some contexts, it may be
+        undesirable to include the URL of the application - the template
+        can be edited to remove this.
+
+    registration_notify
+
+        Sent to the configured registration_notify address when a
+        new user registers to use the system.  Additional variables
+        include {{sponsor}}, and details of the registering user.
+
+    too_many_attempts
+
+        Sent to the configured registration_notify address when a
+        user makes too many attempts to log in with an incorrect
+        password. Additional variables include details of the user.
+
+The sender for generated e-mails can be overridden by inserting a "From"
+header as the first line in the template, for example:
+
+    From: NetEpi Collection <netepi at your.domain>
+    To: {{exception_notify}}
+    Subject: [{{appname}}] Uncaught exception report ({{datetime}})
+
+    {{exception}}
+
+Other RFC822 headers, such as Reply-to: can be overridden in a similar way.
diff --git a/README.mac b/README.mac
new file mode 100644
index 0000000..845276f
--- /dev/null
+++ b/README.mac
@@ -0,0 +1,78 @@
+Installing NetEpi Collection under Apple Mac OS X
+=================================================
+
+Please read the README file first.
+
+Note - these instructions assume the user has advanced command line system
+administration knowledge, and are not for the faint of heart.
+
+ 1) Install PostgreSQL
+
+    We recommend using the Unified Installer Disk Image from:
+
+        http://www.postgresqlformac.com/
+
+    It may also be possible to use the OS X PostgreSQL packages from the
+    PostgreSQL project, although we haven't tested this:
+    
+        http://www.postgresql.org/download/macosx
+
+    The Fink and MacPorts systems also provide PostgreSQL packages.
+
+ 2) install Albatross
+
+    Download the Albatross tar-ball from:
+
+        http://www.object-craft.com.au/projects/albatross/download.html
+
+    Unpack it to a directory then, in that directory, run:
+
+        sudo python setup.py install
+
+    For instructions on configuring the Albatross session server to
+    start at boot, see the file mac/README in the Albatross directory.
+
+ 3) install eGenix mx Base
+
+        sudo easy_install egenix-mx-base
+
+ 4) install ocpgdb
+
+        sudo easy_install ocpgdb
+
+ 5) configure web server
+  
+     a) turn on Personal Web Sharing (System Preferences->Sharing)
+
+ 6) Optionally install mod_fastcgi (improves application speed considerably):
+ 
+    1) download the Apache mod_fastcgi module source:
+
+        http://www.fastcgi.com/dist/mod_fastcgi-2.4.6.tar.gz
+
+    2) unpack the tar file, and change into the resulting directory
+
+    3) compile the module:
+
+        apxs -o mod_fastcgi.so -c *.c
+
+    4) install the module:
+  
+        apxs -i -a -n fastcgi mod_fastcgi.so
+
+    5) create /etc/apache2/other/fastcgi.conf and add the following:
+
+        # AddModule mod_fastcgi.c
+        <IfModule mod_fastcgi.c>
+            # is this the best option?
+            FastCgiIpcDir /tmp 
+            FastCgiConfig -idle-timeout 300
+            <Directory /Library/WebServer/CGI-Executables/collection>
+                AddHandler fastcgi-script .py
+                Options +ExecCGI
+            </Directory>
+        </IfModule>
+
+    6) reload apache:
+
+        apachectl configtest && apachectl graceful                                        
diff --git a/README.selinux b/README.selinux
new file mode 100644
index 0000000..2b2f800
--- /dev/null
+++ b/README.selinux
@@ -0,0 +1,21 @@
+Possible SELinux issues
+=======================
+
+Under Red Hat Enterprise Linux Server release 5.1 with SELinux
+active, installed files may need to be in the httpd_sys_script_exec_t
+context and may be set using
+
+    chcon -t httpd_sys_script_exec_t ...
+
+The notification socket directory (db/notification) may need to be
+created by hand and needs to have a context of httpd_sys_script_rw_t
+
+    chcon -t httpd_sys_script_rw_t notification
+
+
+
+--
+For more information on SELinux, see:
+
+    http://selinuxproject.org/
+    http://docs.fedoraproject.org/selinux-faq-fc5/
diff --git a/Selenium/CollectionTest1.html b/Selenium/CollectionTest1.html
new file mode 100644
index 0000000..c0213c7
--- /dev/null
+++ b/Selenium/CollectionTest1.html
@@ -0,0 +1,1240 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 1 - initial "smoke" test</title>
+  <!-- General test of most, but not all functions, intended as a first-pass 
+       "smoke" test (that is, when you plug it in and turn on the power, it 
+       doesn't crash and burn in a huge cload of smoke). Subsequent tests
+       cover specific functions more thoroughly.
+       Designed to be run using Selenium v0.3 or later. -->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 1<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Thank you for using NetEpi Collection. Your session is now closed.</td>
+      <td> </td>
+    </tr>
+
+    <!-- Login again and create a new case of SARS -->
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+    <!-- Add a new case of SARS -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>new:2</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Add Case</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Please search to make sure the person you are going to add is not already in the database.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>do_search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Results</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>new_case</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Local Case ID</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Onset Date</td>
+      <td> </td>
+    </tr>
+    <!-- Fill in the details of the new case -->
+    <tr>
+      <td>type</td>
+      <td>local_case_id</td>
+      <td>A12345</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>surname</td>
+      <td>humperdinck</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>given_names</td>
+      <td>engelbert</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>DOB</td>
+      <td>23/4/39</td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>sex</td>
+      <td>Male</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>home_phone</td>
+      <td>9876 5432</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>work_phone</td>
+      <td>9123 4567</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>street_address</td>
+      <td>123a smith st</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>locality</td>
+      <td>smithFIELD</td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>state</td>
+      <td>Queensland</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>postcode</td>
+      <td>4567</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>onset_datetime</td>
+      <td>12/9/04 12:34</td>
+    </tr>
+    <!-- Save the data and verify -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Onset symptoms (SARS)</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>surname</td>
+      <td>HUMPERDINCK</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>given_names</td>
+      <td>Engelbert</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>DOB</td>
+      <td>23/04/1939</td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Age: 66 yrs</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>sex</td>
+      <td>M</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>home_phone</td>
+      <td>9876 5432</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>work_phone</td>
+      <td>9123 4567</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>street_address</td>
+      <td>123a Smith St</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>locality</td>
+      <td>SMITHFIELD</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>state</td>
+      <td>QLD</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>postcode</td>
+      <td>4567</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>onset_datetime</td>
+      <td>12/09/2004 12:34</td>
+    </tr>
+    <!-- Check the audit trail log -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>log</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Log</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>surname:humperdinck, given_names:engelbert</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <!-- Change the Notification Status field -->
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Edit Case</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>notification_status</td>
+      <td>Notified to DOH</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <!-- Check the log again -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>log</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Log</td>
+      <td> </td>
+    </tr>
+    <!-- NB Changes to notification_status not currently being logged 
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>notification_status:XXXX</td>
+      <td> </td>
+    </tr>
+	-->
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Edit Case</td>
+      <td> </td>
+    </tr>
+    <!-- Add data to SARS Symptoms Onset form -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>new:sars_symptoms</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Fever >38 degrees C</td>
+      <td> </td>
+    </tr>
+    <!-- Note that it is not possible to set radio buttons reliably yet -->
+    <tr>
+      <td>type</td>
+      <td>form_sars_symptoms_00000.fever_onset</td>
+      <td>12/3/04</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_symptoms_00000.onset_first_symptom</td>
+      <td>13/3/04</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_symptoms_00000.xray_date</td>
+      <td>14/3/04</td>
+    </tr>
+    <!-- Save the data -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>form_submit</td>
+      <td> </td>
+    </tr>
+    <!-- Check that the summary shows correctly -->
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Fever >38C: Unknown, Fever onset date: 12/3/04, Productive cough: Unknown, Difficulty Breathing: Unknown, Headache: Unknown, Myalgia: Unknown</td>
+      <td> </td>
+    </tr>
+    <!-- And check that there is an entry in the log -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>log</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Log</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>form_sars_symptoms_00000[summary_id:None, fever:Unknown, fever_onset:12/03/2004, onset_first_symptom:13/03/2004, productive_cough:Unknown, nonproductive_cough:Unknown, diff_breathing:Unknown, headache:Unknown, myalgia:Unknown, diarrhoea:Unknown, sore_throat:Unknown, rigors:Unknown, nausea_vomiting:Unknown, dizziness:Unknown, xray_date:14/03/2004, xray_pneumonia:Unknown, xray_description:xray3, autopsy_rds:Unknown]</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Edit Case</td>
+      <td> </td>
+    </tr>
+    <!-- Test access control lists -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>access</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Access</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Administrator Unit</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>pt_search_go</td>  <!-- Note unusual name for search button...-->
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>South Eastern Sector Laboratory Service</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>add_4</td>  <!-- Click the add arrow for above unit -->
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>South Eastern Sector Laboratory Service</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <!-- Check new ACL -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>access</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Access</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Administrator Unit</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>South Eastern Sector Laboratory Service</td>
+      <td> </td>
+    </tr>
+    <!-- Try to remove access by own unit -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>remove_0</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td> Can't delete your unit's access</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>cancel</td>
+      <td> </td>
+    </tr>
+    <!-- Now test contacts -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>This case has no contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>add_contact</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Add Contact to Case</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>do_search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>seconds and returned</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>new_contact</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Edit Case Contact</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Enter details of contact</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>A surname must be given</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.surname</td>
+      <td>flack</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.given_names</td>
+      <td>roberta</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.DOB</td>
+      <td>17/9/1947</td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>contact.person.sex</td>
+      <td>Female</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.home_phone</td>
+      <td>1234 5678</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.work_phone</td>
+      <td>8765 4321</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.street_address</td>
+      <td>98 middleofthe rd</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.locality</td>
+      <td>smaltzville</td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>contact.person.state</td>
+      <td>Tasmania</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.person.postcode</td>
+      <td>7654</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.contact_row.contact_date</td>
+      <td>29/11/03</td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>contact.followup_type</td>
+      <td>Active follow-up</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Contact date must be after onset date</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>contact.contact_row.contact_date</td>
+      <td>29/11/05</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Edit Case Contact</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS Contact Follow-up</td>
+      <td> </td>
+    </tr>
+    <!-- Verify the values -->
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.surname</td>
+      <td>FLACK</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.given_names</td>
+      <td>Roberta</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.DOB</td>
+      <td>17/09/1947</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.sex</td>
+      <td>F</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.home_phone</td>
+      <td>1234 5678</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.work_phone</td>
+      <td>8765 4321</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.street_address</td>
+      <td>98 Middleofthe Rd</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.locality</td>
+      <td>SMALTZVILLE</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.state</td>
+      <td>TAS</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.person.postcode</td>
+      <td>7654</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.contact_row.contact_date</td>
+      <td>29/11/2005</td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>contact.followup_type</td>
+      <td>active</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>new:sars_followup</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Does the contact have any of these symptoms</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_followup_00000.first_temperature</td>
+      <td>16.3</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_followup_00000.second_temperature</td>
+      <td>39.3</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_followup_00000.other_comments</td>
+      <td>The rain in Spain falls mainly on the aeroplane.</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>form_submit</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>ERR: must greater than or equal to</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>form_sars_followup_00000.first_temperature</td>
+      <td>36.3</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>form_submit</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>1st Temp: 36.3</td>
+      <td> </td>
+    </tr>
+    <!-- Now change user's details -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>home</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>yourdetails</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>User details</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Retype New Password</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user.email</td>
+      <td>admin at netepi.info</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Please enter a password</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Definition</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>difficulty breathing</td>
+      <td> </td>
+    </tr>
+    <!-- Check that the details have changed -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>yourdetails</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>User details</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Retype New Password</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>user.email</td>
+      <td>admin at netepi.info</td>
+    </tr>
+    <!-- Now try to change password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Please enter a password</td>
+      <td> </td>
+    </tr>
+    <!-- Now try to change password with non-matching new password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn12Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Passwords don't match - try again</td>
+      <td> </td>
+    </tr>
+    <!-- Now try to change password with a weak new password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>snooPy</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>snooPy</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>at least two alphabetic</td>
+      <td> </td>
+    </tr>
+    <!-- Now try to change password with another weak new password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn00py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn00py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Alphabetic characters in the password must be a mix of upper and lower case</td>
+      <td> </td>
+    </tr>
+    <!-- Now try to change password with wrong old password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Incorrect password</td>
+      <td> </td>
+    </tr>
+    <!-- Now really change password -->
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Case Definition</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>difficulty breathing</td>
+      <td> </td>
+    </tr>
+    <!-- Log out and try to log back in -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Your session is now closed.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Incorrect password</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>area with community</td>
+      <td> </td>
+    </tr>
+    <!-- Change password back to what is was -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>yourdetails</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>User details</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Retype New Password</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyValue</td>
+      <td>user.email</td>
+      <td>admin at netepi.info</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user.email</td>
+      <td> </td> <!-- Reset field to blank otherwise this test will fail when run again. -->
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>old_password</td>
+      <td>sn11Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_a</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>new_pwd_b</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>deep seated vesicles</td>
+      <td> </td>
+    </tr>
+    <!-- Check that searches are working -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Cases & Contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>do_search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Results</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search took</td>
+      <td> </td>
+    </tr>
+    <!-- Search for someone who doesn't exist. -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>home</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Cases & Contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>search.person.surname</td>
+      <td>engelbert;</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>do_search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Results</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>HUMPERDINCK</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <!-- Test filtering by syndrome -->
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Cases & Contacts</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>select</td>
+      <td>search.syndrome_id</td>
+      <td>Smallpox</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>do_search</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Search Results</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>No matches found</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>An illness with acute onset of fever</td>
+      <td> </td>
+    </tr>
+    <!-- Check export page as far as possible, which is not very far. -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>export</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Select export scheme</td>
+      <td> </td>
+    </tr>
+    <!-- That's enough for Test 1 -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Your session is now closed.</td>
+      <td> </td>
+    </tr>
+  </tbody>
+</table>
+</body>
+</html>
diff --git a/Selenium/CollectionTest2.html b/Selenium/CollectionTest2.html
new file mode 100644
index 0000000..b062b26
--- /dev/null
+++ b/Selenium/CollectionTest2.html
@@ -0,0 +1,510 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 2 - user account creation and manipulation tests</title>
+  <!-- Test of account request/enabling functions.
+       Designed to be run using Selenium v0.3 or later. -->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 2<br>
+      </td>
+    </tr>
+    <!-- Go to login page -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>register</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Register for an account</td>
+      <td> </td>
+    </tr>
+    <!-- Now change user's details -->
+    <tr>
+      <td>type</td>
+      <td>username</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>fullname</td>
+      <td>Selenium Test User</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>role</td>
+      <td>Just testing...</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwda</td>
+      <td>sn33P</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwdb</td>
+      <td>sn33P</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>email</td>
+      <td>selenium at netepi.info</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_wk</td>
+      <td>1234 5678</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_mbl</td>
+      <td>04123456789</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_hm</td>
+      <td>9876 5432</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_fax</td>
+      <td>5432 9876</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Password must be at least 6 characters</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwda</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwdb</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <!-- Now try to log in as new user -->
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>User not enabled</td>
+      <td> </td>
+    </tr>
+    <!-- Now enable the new user -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/admin.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/admin.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>ADMINISTRATION<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Administration</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>by form name</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user_search_name</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>user_search_go</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Selenium Test User</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>edit_user_0</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>User 'selenium_test_user'</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>click</td>
+      <td>user_enabled</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Cannot enable a user who is not a member of at least one unit</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pt_search.search_term</td>
+      <td>airport*</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>pt_search_go</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Airport Medical Centre</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>add_0</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user.role</td>
+      <td>Just testing....</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>back</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>ADMINISTRATION<br>
+      </td>
+      <td> </td>
+    </tr>
+    <!-- Now test the newly enabled user. -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Selenium Test User<br>
+      </td>
+      <td> </td>
+    </tr>
+
+    <!-- Log out and try to re-register as same user -->
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Your session is now closed.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>register</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Register for an account</td>
+      <td> </td>
+    </tr>
+    <!-- Now change user's details -->
+    <tr>
+      <td>type</td>
+      <td>username</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>fullname</td>
+      <td>Selenium Test User</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>role</td>
+      <td>Just testing...</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwda</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>pwdb</td>
+      <td>sn33Py</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>email</td>
+      <td>selenium at netepi.info</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_wk</td>
+      <td>1234 5678</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_mbl</td>
+      <td>04123456789</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_hm</td>
+      <td>9876 5432</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>phone_fax</td>
+      <td>5432 9876</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td> Sorry, that user name is already used - pick another</td>
+      <td> </td>
+    </tr>
+
+    <!-- OK, delete the test user account. -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/admin.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/admin.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>ADMINISTRATION<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Administration</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>by form name</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user_search_name</td>
+      <td>selenium_test_user</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>user_search_go</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Selenium Test User</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>delete_user_0</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>update</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>logout</td>
+      <td> </td>
+    </tr>
+
+  </tbody>
+</table>
+</body>
+</html>
diff --git a/Selenium/CollectionTest3a.html b/Selenium/CollectionTest3a.html
new file mode 100644
index 0000000..ab886db
--- /dev/null
+++ b/Selenium/CollectionTest3a.html
@@ -0,0 +1,20059 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 3a - load synthetic cases</title>
+  <!-- Loads a few hundred synthesised cases. The names and addresses were
+       synthesised using the dbgen utility from the Febrl v0.3 open source
+       probabilistic record linkage suite, which is available from
+       http://febrl.sourceforge.net Any resemblance of the names and addresses
+       in this test to real persons is entirely coincidental.-->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 3a - load synthetic cases<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8524295</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cooper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bollen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 brereton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bayswater north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5201</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/05/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 33247849</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2976749</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>peter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcneill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>39 newman morris circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wellington point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2760</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/07/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 08679144</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9572659</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fuda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 medley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>scarborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2540</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 55793401</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3251691</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brooke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>spark</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>200 edith place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cooktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3101</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/09/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 93001619</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8886129</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bridget</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>noyce</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>71 windradyne street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>boondall</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3139</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/02/1902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 90889947</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7973529</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 boldrewood street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kirrawee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2653</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/03/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 02493932</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2973347</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sebastian</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>noble</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>89 hyndes crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>montmorency</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2551</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/12/1926</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 82324899</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4115305</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>gabriella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stevanja</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>oakleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6149</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/12/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 54749412</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4466325</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ethan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>spicer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 lipscomb place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dowerin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2485</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/06/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 18333836</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5860995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caleb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 postle circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>chinchilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7052</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/09/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 88492389</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5634576</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rudd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>266 osborne place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount isa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3040</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/11/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1717829</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>brennand</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>37 wrixon street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>craigie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3083</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/04/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 22187437</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7197420</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>antonio</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boulter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>107 sullivan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>forrestfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3109</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/01/1930</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 50835171</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7023549</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sarah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>132 sturdee crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wagaman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4849</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 91701154</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8946191</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>timothy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chandler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 quiros street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gwandalan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2141</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/04/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 75298657</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2944011</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>werk</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 carter crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>port fairy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3165</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/01/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 45286407</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8841589</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kandunias</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 forbes street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>daisy hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/07/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 32047645</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8476149</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jessica</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 michie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mcdowall</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3915</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/03/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 58265176</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7611575</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isobel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>heerey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>50 smeaton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bunbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7006</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/09/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 68690208</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5756415</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jasper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>53 crisp circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kirwan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3214</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/09/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6571249</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlize</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bouras</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>44 melrose drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>stephens</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2464</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/10/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 50204708</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7515355</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>yasmin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>purseglove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 woodburn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mermaid waters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4720</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/04/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 23973495</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3946140</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>livia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>procter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>115 percival street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>waroona</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4216</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 84241582</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1792550</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>340 the causeway</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>byford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6107</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/11/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 78292529</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1158086</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>noah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>simmonds</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 ashburton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bollon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4720</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/08/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 18379623</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1132764</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>luka</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mckane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 marcus clarke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>burwood east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2264</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/01/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 75069701</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4599553</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 flowerdale place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>magill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4878</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/01/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 28832050</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1966822</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>149 eppalock street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wantirna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6027</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/07/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 43060044</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2844379</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mattheo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lippett</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>33 mckenzie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beechboro</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5251</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 31769689</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8365123</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>imogen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sherriff</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 landsborough street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ormond</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1702556</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wegman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>eden hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2166</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/06/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 56252227</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4176213</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>284 strangways street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>thornlie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2147</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/04/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 01014025</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8510812</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samuel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>610 albermarle place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>south perth</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6111</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/08/1915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 31812357</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5949177</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vimpany</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 walu street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hoppers crossing</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2142</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/05/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 04824014</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4029750</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>amy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 theodore street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st albans</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6707</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/03/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 64369413</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9121926</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>garcia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>269 glencross street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>woorndoo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3976</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/01/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 91349406</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8410538</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>herbert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>366 bernacchi street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>south melbourne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6056</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/05/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 08648167</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7529410</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brody</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 john cleland crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rossmoyne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3159</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/10/1915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9300546</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>goldsworthy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>36 nungara street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>melville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2062</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/07/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 16589371</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1940206</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alexandra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>miles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>harbord</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2538</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/03/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 67617528</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2527302</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kai</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 blamey crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>penrith</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4810</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/09/1921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 53010222</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4208695</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zachary</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>corones</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>33 marrakai street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>charmhaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3106</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/05/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 47824379</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4765434</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>todd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cairns</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4034</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/02/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 69466787</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5424625</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kelsey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whiteley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 ballumbir street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount isa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4352</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/05/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 85052661</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4190428</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ethan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>gillard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 galloway street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mossman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2340</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/06/1902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 43796789</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8160339</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>marcus</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 kirwan circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moorabbin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4109</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/10/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 77886421</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8525700</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wooley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 caley crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>jurien bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6055</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/12/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 06886691</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4022752</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kelsye</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>patience</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 freda bennett circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ballina</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/07/1961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 20842804</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3418301</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>walls</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>73 bingham circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rookhurst</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2160</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/12/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 41458536</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7740714</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tiana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>warneke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>34 boswell crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cooloongup</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2680</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/05/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 63815665</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8911754</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>michael</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bouras</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 blamey crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>crystal brook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/08/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 44677387</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1564143</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>christian</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>72 bourne street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6064</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/10/1902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 33617314</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3052251</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcveigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 hartigan garden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mayfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2756</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/03/1962</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 58309186</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4708456</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brooklyn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rees</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 carss place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>chelsea heights</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5032</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 11323850</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5347202</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vanbelkom</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 dorrit black crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ourimbah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2041</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/12/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 36243141</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4670965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kenneth</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blackwell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> hann street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wantirna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3460</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/11/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 85014811</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9638807</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matthew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coleman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 jevons place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2218</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/07/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 50247437</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1605412</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mihelcic</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 leahy close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lavington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/08/1950</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 45921925</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3538542</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>latham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stoker</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 neeld place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>peel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2207</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/12/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 24356881</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1179463</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lachlan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>miliano</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 rosenthal street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>oakville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3429</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/05/1989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 43471197</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1194945</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bridget</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stanley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>256 baudin street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>deagon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3821</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/02/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 41613314</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3561300</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>vianca</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rothe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>81 michell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lowood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2325</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/02/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 55387062</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7210649</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>krystin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 hansen circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>creswick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2528</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/08/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 75575113</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7193460</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alexandra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 coolibah crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>warilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2070</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/03/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 59440400</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7811540</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>40 kavel street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mermaid waters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3048</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/01/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 72888035</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4640385</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>holly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vig</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 diggles street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>windsor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5039</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/09/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 41366710</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2765932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>galantomos</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 ross smith crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st lucia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5700</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/03/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 37620963</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6677294</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>luke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>durbridge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>811 point hutt road</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kennington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3850</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/04/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8166523</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>deakin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>orban</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 bareena street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mulgrave east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2145</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/05/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 07499640</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2503057</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stephanie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 lachlan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>braddon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2027</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/04/1933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 44243911</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2771397</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>georgia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hendricks</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>56 hall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>myponga</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2229</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/05/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8525472</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jonah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chandler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 derwent street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bonython</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2251</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/11/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 42505332</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9308652</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>miranda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>zilm</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>95 callabonna street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>brighton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5606</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/03/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 51132710</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9195328</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zachary</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>karanikolis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 seaver street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mentone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6112</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/06/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 59723386</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5865608</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cooper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>huddy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 jauncey court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dynnyrne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2067</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/09/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 86923072</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3048421</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>blake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 baker garden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>otford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2281</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/12/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 49426353</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5057322</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>haylee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>henderson-wilson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 darmody street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tuart hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6081</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/07/1938</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 01662480</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4743218</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mccarthy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 strickland crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>forrestfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5082</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/02/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 37808253</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8683696</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>abbey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kaspar</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>132 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>belmont</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2193</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/09/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 70196769</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6087321</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sheaffer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 braine street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mulgrave east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3184</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/09/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 24440779</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7975417</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>maggie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cambridge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2112</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/06/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 07943056</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5122502</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schumann</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>35 habgood place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>applecross</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3584</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/12/1945</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 41120137</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1385083</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>di manno</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 fullagar crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glengowrie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2470</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/09/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 62414884</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3597509</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>211 haswell place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>goorambat</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4477</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/06/1905</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 46781348</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2680845</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>diekman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>133 mccoll street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>keysborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2541</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/04/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 18904122</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4183760</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tynan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>camp</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>453 birchall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leichhardt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7018</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/06/1992</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 15606653</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4715537</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jinni</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>majid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>camperdown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4390</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/03/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 25148848</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2907898</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 hampton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>vermont</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2450</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/08/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 37033594</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1329214</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>talia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>plane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 buntine crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>devonport</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4343</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/01/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 89233168</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4290666</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>110 alberga street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>thornbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5067</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/04/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 97808211</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4891544</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>drysdale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 sloane place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>surry hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2630</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/07/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 39165885</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6568413</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>harkotsikas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 kingsford smith drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bundaberg</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2097</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/07/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 85702649</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8493344</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>solomon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 millhouse crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>revesby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2219</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/08/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 56588088</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5203468</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>krone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 shumack street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>klemzig</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/10/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 95027269</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5733886</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>robert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>petersen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 totterdell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>terrigal</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3036</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/07/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 54258389</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8231730</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ashleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>van de water</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>127 emmott place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>homebush</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3440</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/04/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 39510440</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8175780</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>grace</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hajda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>82 blumenthal place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toormina</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2113</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 95994351</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4149439</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 starke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bourke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6054</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/10/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 43575659</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4174265</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcvilly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 beddome place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2390</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/03/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 57533738</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2046652</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>liam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>34 namatjira drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glenbrook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3678</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/01/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 29291875</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6564525</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>logan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>worthington-eyre</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 mccombie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>revesby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2287</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/03/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 43047868</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8921845</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sheldon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 berrell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>seaview downs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4211</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/05/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 30568409</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3524009</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tyron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>gimbrere</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2037</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/01/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 36812341</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7658463</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matilda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bonvino</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>369 boulton close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>darlington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4677</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/09/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 04177658</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8054556</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mackenzie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>garcia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 goyder street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gurley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2217</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/10/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 65731940</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3921576</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>barkly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lukeman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>418 spafford crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ashfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2148</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 79506129</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8764578</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lewis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>leesue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 medley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>scarborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5280</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/12/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 65349631</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4016095</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chantelle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>longhurst</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 tenison-woods circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>penshurst</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4034</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/06/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 52085944</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4250832</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>96 macalister crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bayswater</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2153</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/01/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 61269829</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5438504</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 springvale drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mayfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6157</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/04/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 52400729</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6361642</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mitchell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ladiges</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 starke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount isa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4211</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/08/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 99836973</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7735129</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>india</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hobson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>56 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>killara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2705</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/09/1919</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 43631235</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8506005</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kiera</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stubbin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 laman place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kalbar</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2323</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/06/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 43895238</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4530433</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emma</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 hurley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greens beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2500</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/09/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 35743479</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8860759</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jackson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>morrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>211 pullar place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>smithfield plains</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4006</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/06/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 57555939</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5708929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alessia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coleman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 crossley close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>marsden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4211</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/05/1970</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 25103238</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6658594</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>roberti</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>96 shackell place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dorrigo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2126</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/03/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 70572930</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5509965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lydia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>verner</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>283 port jackson circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>batemans bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3132</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/09/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 28663272</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3419461</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kelsy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 huelin circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bowen hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4011</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/11/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 76633300</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3280549</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shannon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>van beek</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 numbat place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>terrigal</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4068</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/11/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 76531247</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1520660</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>gianni</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>loveland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 webster street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kardinya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2166</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/01/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 25802143</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2677896</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sachin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>noble</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 maclean street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>berwick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2607</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/01/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 10968691</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1866642</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kirra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>castellari</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>461 vonwiller crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beelbangera</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6330</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/08/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 50399192</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6128180</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 fawkner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>telegraph point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3015</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/09/1929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 54939334</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6395986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>madeleine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>liapis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 mcbride place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beaumaris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3168</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/09/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 10449936</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2670825</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>giuliana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>scarce</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>67 farrer place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hoppers crossing</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4505</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/04/1936</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 05311090</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3037530</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samuel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>subramaniam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 nicklin crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2131</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/07/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 81179925</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7979639</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ava</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>holgate</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 blackman crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mill park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4810</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/04/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8738787</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcmullen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>92 ebenezer street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hurstville south</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2259</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/05/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 05816592</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8326573</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>xanthe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>gazzola</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 kalgoorlie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cowell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2144</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/01/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 95940748</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8842006</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 northmore crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>picnic point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2317</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/01/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 42483346</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4266648</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tuckwell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 backhouse street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>jervois</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2076</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/08/1952</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 02967955</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3715529</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>byers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 wakefield garden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beaumaris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4370</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/01/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 61156744</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8783289</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlotte</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 stenhouse close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>landsborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5068</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/05/1998</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 32610187</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8519479</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alice</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>191 culgoa circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mansfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4551</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/03/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 19240203</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7661267</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zachary</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>turale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>90 eyre street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>waterloo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2037</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/07/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 59601360</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3807902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlotte</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schumann</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 mirrool street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>roxburgh park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5015</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Northern Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 00470469</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2875534</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coppock</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 jaeger circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bangor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2010</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/08/1994</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 46523436</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2695598</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jessica</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>petersen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 rumbelow court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>miami</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2777</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/06/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 98462943</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2290778</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cameron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lodge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>50 percy crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>edithvale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5127</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/11/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 77113754</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2642153</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bailey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>55 dawes street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>yarraville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4207</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/04/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 05161948</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9443855</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>demie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blunden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>55 yarrow place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kirrawee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2340</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/05/1921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 02336291</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1404998</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tarshya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 marungal avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rankin park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4161</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/09/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 03509054</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8772939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aleesha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>35 rivett street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2251</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/04/1977</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 97023933</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3249106</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>maddison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>harrington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 hall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>jamberoo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 42456495</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3147785</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samuel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>morrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 livingston avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>punchbowl</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/04/1904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4918434</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>olivia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>golder</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>71 port jackson circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>como</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6007</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/02/1938</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 03753965</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1396376</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>berry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 harry hopman circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>karratha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3226</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/01/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 76726462</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5870940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kai</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 cuthbertson crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leichhardt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2759</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/03/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 17233150</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3314526</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matthew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nieuwendyk</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 bennelong crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>old beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4816</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/06/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 64663264</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2342936</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>feeley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 richardson circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>maryborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3216</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/09/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 46237574</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6307092</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>70 blackwood terrace</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>new town</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3730</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/07/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 58360492</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2439512</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>suffolk</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 canning street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moorabbin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2484</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/09/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 85465002</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8781757</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>weaver</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>52 melrose drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bicton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/11/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 23232865</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6884486</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>georgia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 fincham crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>warracknabeal</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2444</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/03/1927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 39883840</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5222532</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>erin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 forrest place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>christies beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5690</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/05/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 94323363</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3998036</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>finlay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 barada crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3146</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/01/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 08771914</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7867690</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bartel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 flinders way</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4817</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/07/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 73059967</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3598955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 london circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount evelyn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2285</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/06/1980</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 25300087</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1370599</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sedunary</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>93 toms crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>birchgrove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4702</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/01/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 84596044</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5637375</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>coby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stephenson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 couvreur street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3812</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/12/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3423112</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>luke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kowald</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>reynella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2474</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/12/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 79455644</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9533647</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 lee-steere crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>parkinson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7315</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/08/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 81473701</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6730524</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>holly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fenwick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 staaten crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>baxter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3038</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/02/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 98579278</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4971541</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 beauchamp street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>spearwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>0820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/07/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 24393050</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+            <!-- Return to home page -->
+            <tr>
+              <td>clickAndWait</td>
+              <td>logout</td>
+              <td> </td>
+            </tr>
+    
diff --git a/Selenium/CollectionTest3b.html b/Selenium/CollectionTest3b.html
new file mode 100644
index 0000000..14db9f7
--- /dev/null
+++ b/Selenium/CollectionTest3b.html
@@ -0,0 +1,19575 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 3b - load synthetic cases</title>
+  <!-- Loads a few hundred synthesised cases. The names and addresses were
+       synthesised using the dbgen utility from the Febrl v0.3 open source
+       probabilistic record linkage suite, which is available from
+       http://febrl.sourceforge.net Any resemblance of the names and addresses
+       in this test to real persons is entirely coincidental.-->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 3b - load synthetic cases<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1043590</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>aiston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 pitcairn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>berkeley vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/10/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 31496795</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3056304</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>petreece</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fullerton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 crozier circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st albans</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3620</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/10/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 65116054</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7271418</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>oliver</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 hirschfeld crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mullaloo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3909</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/03/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 36551612</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4105513</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>finlay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>30 woodger place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>winston hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6028</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/01/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9144785</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 ivo whitton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>creswick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3135</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/11/1954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 76006872</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6848420</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>courtney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bartos</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 mcginness street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>oxley vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3115</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/02/1919</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4812180</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>noah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mewett</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>67 bullock circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>birkdale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7260</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/01/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 66099844</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1142563</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jazz</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bellchambers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 cartwright street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>edithvale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5022</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/03/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 27757048</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6563352</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tansy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dooley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 mccrae street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st kilda east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3444</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/10/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 10625378</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8322174</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>elizabeth</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>donaldson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 hetherington circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>garran</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4701</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/11/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 26515007</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6857876</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>calvin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>heathfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>66 stokes street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flemington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3011</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/05/1962</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 31481365</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9545481</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 dore court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dareton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2037</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/02/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93067011</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1638791</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>alty</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>126 campbell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>elwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2768</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/07/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 05984793</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7374559</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>57 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kyogle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3021</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/03/1992</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 21787477</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1557460</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stubbs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 blamey crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leichhardt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2125</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/10/1924</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 73771322</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6531121</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aurora</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>orkney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 grayson street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>humpty doo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5255</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/05/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 91590223</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1569542</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kaitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>edmeades</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 gidja place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>atherton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3171</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 20254296</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2445476</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mason</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>melzner</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 mirning crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>deer park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2026</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/08/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 66673273</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5911940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bridget</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lodge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 partridge street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sherwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/12/1927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 31475642</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2352315</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kayla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bambling</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 alberga street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>eastwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3205</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/06/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 27274466</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8383518</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>freya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>holberry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>50 white place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hammondville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3172</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/10/1998</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 28027461</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6564989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>scott</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mahony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>83 caley crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5353</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/11/1992</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 44312675</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6785588</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>burford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 hollway street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cleveland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3630</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/12/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 95659691</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5574130</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hawes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 troughton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>knoxfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2444</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/09/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 85855993</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9132933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kobe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>crook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 buvelot street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st albans</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2168</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/04/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 99477583</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5694167</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cooper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>200 malcolm place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>melton south</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2229</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/01/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 80753484</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6302349</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brianna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hinchey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 carina street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sandy bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4405</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/06/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 81342374</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4545217</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>leslie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>57 mcilwraith close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>jimbour</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3018</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/02/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8868927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pereyra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>30 denman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>marmion</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2260</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/02/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 83252057</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7873850</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>larissa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>perin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 gidjili place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2780</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/08/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 77430874</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3937168</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alaiyah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lodge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 dane close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>menindee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4740</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/02/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 80413758</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1597882</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lucy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> beauchamp street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>labrador</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4805</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/10/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 52354539</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4878800</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>weidenbach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 arthur circle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3156</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/10/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 99315695</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1440753</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>michaela</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rundle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 jensen street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>valla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/11/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 62377947</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9578229</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>leon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>grainger</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>131 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>biggera waters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2291</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/07/1967</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 98213986</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2868219</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>gabriella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lodovici</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 wagga street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>thirroul</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3033</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/07/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1001513</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>oliver</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boxhall</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 springbett street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>balcolyn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2753</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/10/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 46940822</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3583234</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>anika</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 fuhrman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wyongah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2565</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/02/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 39744019</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6066481</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>flynn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mccourtie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 mcnamara street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>naremburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5165</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/05/1904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 92055843</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1999092</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boyes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 windich street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bonnyrigg</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3910</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/08/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 36109157</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7160953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>zaffino</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>76 clara close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>monogorilby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6271</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/02/1952</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2465646</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ebony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>goode</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 hart close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north narrabeen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7330</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/06/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 42348372</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2791366</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isaac</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 ellenborough street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>macquarie fields</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3429</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/04/1931</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 71986564</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1127983</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reece</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>194 dwyer street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>birkdale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5522</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/12/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 96594037</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8085884</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vincent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 blackman crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>urbenville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2460</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/07/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 17404726</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7611585</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stubbs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>267 bremer street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mosman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7316</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/03/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 59854768</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1005470</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>toby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>35 connelly place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>picnic point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3220</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/11/1983</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 46840128</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7931905</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>neneh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>painter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 muntz street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mosman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2614</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/01/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 82877071</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7837405</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jaime</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>apted</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>104 mileham street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>torquay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2470</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/01/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 73217346</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3242238</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>xanthe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcelwee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 blazey place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north rocks</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2403</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/12/1959</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 78845966</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5373917</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>will</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>harrity</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>36 o'grady place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>the entrance north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3216</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/04/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 87426555</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5157942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>finnbar</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>berry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 lockyer street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flemington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6101</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/09/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 80765370</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8470717</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jordan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>orledge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 steinwedel street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beecroft</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4670</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/10/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 95944913</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1273642</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacinta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coleman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>66 lyttleton crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rose bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4211</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/04/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 33721912</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4632730</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matilda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kielczewski</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 kiewa street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hoskinstown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3802</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/05/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 24473293</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1721654</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wilkey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 max henry crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>birkenhead</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3012</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/06/1976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 53665575</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7358137</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>erin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>allsopp</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 temperley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kempsey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5084</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/09/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 97613591</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5623593</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>indiana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>59 hallstrom circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2804</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/01/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 16040262</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4775809</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tamassy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 barron street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cromer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6215</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/01/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 51828922</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9465005</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>beau</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pados</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 gledden street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lowood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3149</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/12/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 28554821</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3654850</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jye</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bellchambers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 carstensz street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mannering park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5018</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/10/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 09481908</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4895500</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alissa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hammer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 arabana street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>blacktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4655</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/03/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 94130903</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3208823</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>berry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 ringrose crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>artarmon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/02/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 48602033</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7618317</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dooley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>107 chandler street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bowral</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3150</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/01/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 09414587</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1570505</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>luke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>browne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>44 dunstan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6102</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/09/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 28798355</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1319237</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacob</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>huxley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>springvale south</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5075</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/08/1945</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 37378542</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4652993</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ebony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>paterson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 lowrie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>seymour</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2760</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/02/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 27925940</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7304753</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>amy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 crowder circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6065</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/05/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 52843961</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2366798</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>imogen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>noble</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 gruner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lance creek</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3550</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/04/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 09509468</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4520613</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>neumann</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 london circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>torrensville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4875</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/07/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 42173257</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5375888</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>liam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cannons</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 cobb crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>punchbowl</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4700</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/07/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 14096029</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6236050</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>flynn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 rickard place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2256</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/01/1994</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 40874640</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6259134</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>airs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>216 bennelong crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>swan hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2770</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/06/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 21139622</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6879434</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>talia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>snelling</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 costello circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>long jetty</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2049</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/05/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7793179</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>taylah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>paterson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 wendy ey place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gosford east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5048</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/12/1954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 06050909</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2325291</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bristow</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>32 william hudson crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2154</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/03/1998</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 97239741</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4357090</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brinley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>berezny</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>87 holmes crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4735</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/09/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 21719188</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4767222</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cooper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hargrove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 bootle place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>heywood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3201</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/08/1904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 30686351</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8584417</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bethany</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clelland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 tregellas crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coleambally</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2536</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/03/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 76311662</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1077658</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>macey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 mckinlay street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broken hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/07/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 43435089</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8614893</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mitchell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>leishman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 edmondson street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>yagoona</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4510</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/12/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 84816188</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5943241</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>riddell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>marsfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4702</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/06/1994</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 48925247</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3448354</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ciaran</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>devriadis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 selwyn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>raleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4124</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/05/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 32791705</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2183299</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lewis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>skirca</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 sparkes close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>o'sullivan beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2257</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/12/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 72258849</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1741912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>amy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coffey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>39 whitelaw street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2256</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/03/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93181660</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8010730</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>griffin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>baars</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> sangster place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>valentine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4151</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/04/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 38208084</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4014749</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 allman circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>loganlea</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2536</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/08/1976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 20996452</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2719511</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>swiggs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>64 mcglinn place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coombabah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2913</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/03/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 45513668</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2674383</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>anthony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>badger</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 canopus crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>monbulk</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5086</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/04/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 35783099</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7969809</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>baynes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 davidson street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ormeau</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2291</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/01/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 72090956</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7180918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 bateman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>taren point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7009</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/07/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 18956608</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5302576</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brooklyn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>everett</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 mainwaring rich circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cranbourne north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4152</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/02/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 55383722</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7710050</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ellie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>udovicic</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 majura avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>renown park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3880</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/06/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 68786620</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9654348</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>phoebe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>herbert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>318 howitt street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>murray bridge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2579</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/05/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 17327611</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6133264</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacob</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>baillie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>313 mackellar crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toronto</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2658</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/09/1952</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 21468611</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9986960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vincent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> leahy close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kidaman creek</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2283</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/04/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 38847735</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8350803</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>verco</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>41 wambool street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>corio</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2756</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/10/1989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 46271742</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7147601</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zarlia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fitzpatrick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 mountain circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kangarilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4551</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/01/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 35076449</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3923707</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>550 o'hagan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2777</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/05/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 41276518</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4795083</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hyland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>59 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>corrimal east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5345</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/03/1976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 88706667</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5725420</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>blake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>miles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>36 mcgivern crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>drummond cove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/11/1949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 67703256</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7629290</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kylee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>colegate</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>90 parkhill street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toorak</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2541</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/12/1967</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 11656571</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6692820</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lauren</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whitters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 minns place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>warnbro</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2770</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/09/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 24550831</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7435722</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adrian</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 breona place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ashfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6027</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/01/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 63465564</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5571713</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>natalie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 beadle place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hinton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4573</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/02/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 99281908</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6396588</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>carla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>robson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 haddon street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>westbrook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4655</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/11/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 94899965</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4716823</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>183 medley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lamington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3318</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/07/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 96631571</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2524773</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ethan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mason</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>86 moss street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>connolly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4000</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/04/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 45473573</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4652725</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mikayla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>marshman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 hannan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>armidale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2263</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/03/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 73686517</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6454260</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jordan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hawes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 howie court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>blacktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5112</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/08/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 38664190</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3531853</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bridgland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>32 cannan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>eastwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5084</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Northern Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/03/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 84141949</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4948049</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>quinn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>limbert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>102 illingworth street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north rocks</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3500</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/05/1902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 44152463</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5468442</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>taylor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>119 ebden street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>roxburgh park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2800</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/05/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 93146609</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6831879</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ethan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>moody</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>47 namatjira drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>charmhaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/03/1954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1243991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>dante</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>alderson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 smeaton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moree</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5086</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/07/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 12687606</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8299712</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jordan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>51 piddington street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wahroonga</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3223</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/02/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 95848967</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8875216</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>roche</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 murranji street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>samford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6066</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/05/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 29366655</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9314257</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>thiessen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 thake place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>the entrance</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3075</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/12/1929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 45586534</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6896141</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>beh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 mcwhae circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>russell lea</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3875</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/06/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 32921189</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9265393</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coppola</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>418 crofts crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greenacre</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3018</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/05/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 09006486</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3692053</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>timara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 alsop close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>suffolk park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3181</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/07/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 92316051</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1950246</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cooper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>torni</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 unaipon avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>riverstone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4069</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/07/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 61733963</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3172088</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kelsye</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>youde</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 schaffer place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>daisy hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5560</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/09/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 09577755</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4413571</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>troy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cure</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>45 harcourt street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>roselands</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/11/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 57214302</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6717623</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mccarthy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 marconi crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>point cook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2750</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/06/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 07615542</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1696950</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>liam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wilkins</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>97 aspinall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>canterbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2205</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 02971451</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3705561</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>finn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kilby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 kenyon circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>slacks creek</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4850</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/07/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 62380188</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6976223</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jamilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>burford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 madigan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bundaberg</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2036</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/12/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 43020644</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7605654</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bree</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boyes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>32 alston street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>roleystone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3556</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/09/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 20221986</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9177812</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lachlan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 hemmings crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>klemzig</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2144</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/01/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 20181558</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5354543</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>angus</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schuster</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 hodgson crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>auburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>0854</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/04/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 62936377</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7759824</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samuel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 tauss place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ingleburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7140</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/12/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 58789136</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8474822</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>peyton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>holme</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 middleton circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hoppers crossing</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2794</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/11/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 41024212</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1050000</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>maddison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>woodbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 mccann street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north ryde</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4825</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/04/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 13462123</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8473729</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wolfenden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>87 ashburner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>figtree</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2422</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/08/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 88920628</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7183848</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 cloncurry street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>chinchilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4309</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/02/1950</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9712392</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brodie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>keri</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 mcmillan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st ives</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3555</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/05/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 65570661</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8411496</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>liersch</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 novar street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>blacktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3065</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/09/1993</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 61315193</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9104090</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tyler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 ballumbir street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st peters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3073</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/10/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 40776528</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5325940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hage</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>117 lexcen avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>doon doon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2580</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/02/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 15471217</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8604472</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>larissa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cure</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 gurner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coolbellup</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4852</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/03/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 81145228</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5096678</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>siggins</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 laptz close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north narrabeen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2176</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/06/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 40757674</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8657349</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>judah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hyland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>406 finniss crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>nairne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6286</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/06/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 99491296</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9136093</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shakira</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>thiry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 clements street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>raymond island</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2323</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/11/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 89029436</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9197117</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>finlay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>emson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 newman morris circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>burwood east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5159</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/02/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 45278629</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5738882</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>saara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boxer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>42 singleton crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beecroft</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2075</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/11/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 52923002</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4058838</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>morrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 burdett crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>burleigh heads</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2168</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/11/1930</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 05383112</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6117516</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 ambalindum street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tully</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3187</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/06/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 35395540</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9995341</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lachlan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>zbierski</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>114 lambie place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cawarral</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2758</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/01/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 55328662</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3232168</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>julia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 black street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hastings</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3013</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/07/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 41980728</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9743470</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mitchell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stepic</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>76 lawes place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coffs harbour</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6230</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/05/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 82497838</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6904455</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samuel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>browne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>96 crocker place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wooragee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7005</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/07/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 66365565</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1581021</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kiria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pride</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>80 dartnell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kardinya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2120</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/06/1983</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 14857836</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1547769</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bradley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 newman morris circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>allora</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5069</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/01/1978</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 11427181</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3057985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>casey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>robson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>64 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>norah head</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3805</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/12/1915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 71469073</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7899747</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>olivia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lathouros</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 faucett street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>braidwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6162</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/07/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 03160474</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9326569</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>megan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tiede</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 barrington crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kurmond</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/07/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 91168884</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7771052</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>braedon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>brock</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2204 marshall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kogarah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2750</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/01/1967</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 05127496</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5779827</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ned</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wimbush</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>37 mcnolty place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st albans</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2076</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/10/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 91438216</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8979654</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lee</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tronc</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>46 wilkie place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rowena</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2450</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/10/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 92039585</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3099559</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ruby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>murry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> arabana street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lakemba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4270</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/07/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 24405591</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+            <!-- Return to home page -->
+            <tr>
+              <td>clickAndWait</td>
+              <td>logout</td>
+              <td> </td>
+            </tr>
+    
diff --git a/Selenium/CollectionTest3c.html b/Selenium/CollectionTest3c.html
new file mode 100644
index 0000000..8b41d06
--- /dev/null
+++ b/Selenium/CollectionTest3c.html
@@ -0,0 +1,19333 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 3c - load synthetic cases</title>
+  <!-- Loads a few hundred synthesised cases. The names and addresses were
+       synthesised using the dbgen utility from the Febrl v0.3 open source
+       probabilistic record linkage suite, which is available from
+       http://febrl.sourceforge.net Any resemblance of the names and addresses
+       in this test to real persons is entirely coincidental.-->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 3c - load synthetic cases<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8855692</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>guymer</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>119 barunga street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>pennant hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4215</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/06/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 07553000</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6113391</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>karli</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 hayley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>preston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4053</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/09/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 96594169</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7004343</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caleb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>alderman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>150 trickett place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>peregian beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3802</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/07/1912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 94021033</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3584803</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>imogen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whiteley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 jalanga crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dunkeld</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5125</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/09/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 01759526</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1984737</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 goyder street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>burnie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4214</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/09/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 40139679</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5957763</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brody</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>henneken</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 st clair place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>atwell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2116</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/05/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 53107854</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4517378</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>baillie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>56 monaghan place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>summer hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4818</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/05/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 69245704</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1454100</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wilkey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 borrowdale street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>crafers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4074</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/03/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 66823876</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5745978</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>meaney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 conder street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>orelia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4407</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/06/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 77981898</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3481876</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tromp</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> ingamells street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dandenong north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2230</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/06/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 30309545</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4644164</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cameron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>jeffries</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>41 brereton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>arana hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3352</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/09/1927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 67690843</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6909864</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zachariah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 langdon avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tarragindi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4055</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/04/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 78884578</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3869322</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lombardi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 drevermann street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>abbotsford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4565</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/03/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 90574702</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8176440</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 bamir square</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>forrestfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2530</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/01/1945</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 62533250</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7886803</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cassandra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 griffith place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>canterbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2148</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/12/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 95162350</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5144071</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bowen-smith</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>78 hadleigh circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>the rock</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5051</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/06/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 46876960</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3017219</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>eglinton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>193 spring range road</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>whalan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2122</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/10/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 81300102</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9848193</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>erin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>leane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 mcburney crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount gravatt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6798</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/11/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 08579737</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8990242</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mason</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>68 boldrewood street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dulacca</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2290</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/12/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 06725506</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2112845</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>maynard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 mckeahnie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2821</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/08/1992</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 19921662</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4152253</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>marleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ziersch</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 monkman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>labrador</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4570</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/07/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 99571514</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8270134</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alicia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>baloban</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 novar street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>colo vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2880</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/07/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 41229238</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3565152</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>barnaby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>oberdorf</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>78 seymour place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2338</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/01/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 41502833</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1060397</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stephanie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cioffi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>29 canaway place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2154</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1980</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 15211144</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2474379</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kirkmoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 rosman circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cherrybrook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3677</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/05/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 82497699</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7939918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 deamer crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flemington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2290</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/04/1928</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 08614418</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2991851</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tyler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 musgrave street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greenwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4350</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/01/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 23416477</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1134344</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cameron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>herbert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 wisdom place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>woy woy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3134</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/08/1942</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 57503321</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4129352</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>55 maharatta circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>adamstown heights</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/01/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 41115222</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3965979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tommy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>netherton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 launceston street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>st ives</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5019</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/10/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 28090772</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4726758</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>grace</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>eskrigge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>330 dugan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wyongah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3377</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/03/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 38306320</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7588558</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adele</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>66 foxall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coldstream</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3758</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/04/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 62921109</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4089306</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>neve</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stolz</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 hopkins street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>millers point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2113</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/05/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 42353698</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5437852</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> nullagine street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kotara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2456</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/12/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 24310162</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9314441</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>rhys</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vincent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 nardoo crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>albion park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2540</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/06/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 48758486</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4288442</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jason</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pontifex</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 henty street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north haven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2160</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/10/1931</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 22360277</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5968987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacqueline</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>longden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>208 dugdale street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ascot vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2340</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/08/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 12823998</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2517309</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>michael</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>maynard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 cheeseman place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cranbourne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3044</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/12/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 64276575</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9365140</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tiarna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>roets</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>96 mckillop circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broken hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3442</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/08/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93330416</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1949292</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>brus</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 langridge street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>blacktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5040</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/08/1989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 12175797</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7854361</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>michail</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 morgan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2066</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 59250427</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2001735</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 phillip avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>midland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6214</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/11/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 47008054</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7349645</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>108 connelly place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>port elliot</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2753</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/03/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 55837848</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8471586</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>reuben</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>colaric</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>136 piddington street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bar beach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/08/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 24041497</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6848991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stubbs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 windradyne street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wangaratta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2766</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/09/1977</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 91735332</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4179814</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coxon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 tenison-woods circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leeton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3047</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/01/1933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 32313330</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4218078</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>makayla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>59 mcmillan crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ashford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7320</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/01/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 97251932</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3895121</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>72 mather street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>niangala</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3350</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/10/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 70219689</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5694347</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schuster</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 bunny street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beecroft</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2281</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/02/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 34856633</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3140026</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kayla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>brooker</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 crafer place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>koo wee rup</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3116</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/01/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 78641854</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8051915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kiria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>colthorpe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 macgregor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>aspley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2444</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/07/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6650567</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ruby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hand</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>84 barringer street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>enoggera</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3166</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/03/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 58804636</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6834587</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>finn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>berry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>42 sherbrooke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>milawa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4854</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/01/1955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 08389801</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9398384</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caleb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>255 templeton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wanneroo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2024</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/11/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 34745016</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6838169</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>rachel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>burford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 sculptor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>west lakes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4871</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/01/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 03263907</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4008439</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lauren</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bradshaw</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>124 folingsby street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>surrey hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2137</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/05/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 06233882</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4746309</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kranz</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>72 blamey crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beauty point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5039</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/05/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 03771411</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1942258</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>amy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>witty</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>58 weber place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tumbarumba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2257</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/10/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 17136964</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7917379</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>roebuck</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 jacka crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lake cargelligo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2290</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/03/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 62466538</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4736164</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>andrea</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>atsaves</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 currie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ascot</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3036</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/12/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 16007303</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8776927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>totagi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 burn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cronulla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3138</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/04/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2822187</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>louise</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>morrish</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 weston street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lange</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4215</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/10/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 11808360</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8803454</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stronach</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 gladstone street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mullaway</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5722</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/12/1905</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 91707887</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8077522</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 hansen circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount eliza</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5070</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/09/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 73355345</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5187249</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jesse</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dietrich</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 albermarle place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flemington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2480</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/05/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 27336654</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6517168</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacob</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rachwal</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>135 beltana road</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>malvern</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>0820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/01/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 59478679</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5100649</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kathleen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 abrahams crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>buff point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4740</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/03/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 29937710</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6645357</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fulgido</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 carslake loop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ashgrove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3137</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/12/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 62318146</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1950191</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stephenson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>164 chevalier street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tusmore</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3844</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/12/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 76893056</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1832293</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cassandra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pascoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 duncan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ermington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2871</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/07/1993</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 69741645</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1124175</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kunoth</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>141 redcliffe street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north bondi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5144</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/05/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 04107935</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6904448</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>thomas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>75 mindarie street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>tarragindi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2126</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/01/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 88440078</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4163547</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jemma</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>block</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 charterisville avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>keysborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4217</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/11/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 35990850</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3956042</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bartel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>112 brooks street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>deniliquin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2485</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/04/1905</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 72379557</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9449811</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alexander</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 barr-smith avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north bondi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3199</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/11/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 07612793</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3234024</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 lexcen avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>atwell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7009</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/12/1958</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 34560682</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7122396</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ava</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>34 lalor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wahroonga</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2829</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/11/1902</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 26886970</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1488183</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cheers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 boolimba crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mill park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4808</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/10/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 89700205</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9277960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>perre</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 bertel crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frenchs forest</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3129</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/04/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 80934042</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3825172</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kirra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>morrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 farncomb place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>margaret river</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3181</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/02/1926</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 92999346</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8486132</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lauren</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>richmond</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 mehaffey crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>fairhaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2320</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/12/1955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 40425320</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2180612</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rundle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>122 namadgi circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wiseleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2030</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/09/1958</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 12251102</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5909683</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>luke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 gilmore crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mullewa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5355</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/04/1978</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 37301949</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3586834</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aliza</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fitzpatrick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>33 carnegie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>robertson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3028</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/12/1970</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 82369995</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6695707</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>murton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> newman morris circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>corowa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6432</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/09/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 25651019</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8345396</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stelio</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chiappetta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 carter crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>casino</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/07/1926</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 81875693</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5700843</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>noah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>gamlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>104 were street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broadbeach waters</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3058</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/02/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 48624195</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9903530</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>danielle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>trenerry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>399 ellenborough street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kyeemagh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2620</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/04/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 70307214</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9402826</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>phoebe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schuster</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>250 moorhouse street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2077</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/01/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 56296719</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3099114</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alec</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>presley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>185 outtrim avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ormond</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2754</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/09/1903</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 88406886</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2087166</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kerley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 mcintyre street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>petersham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2204</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Northern Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/11/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 59380683</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3348910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>paul</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>orchard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 ovens street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mill park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6103</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/09/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 11259020</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5882801</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>beattie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dysart</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2153</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/06/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 50487563</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4093625</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>doman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>101 jaeger circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>woy woy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2650</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/02/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 64111014</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2659753</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>prideaux</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 naismith place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sunshine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2611</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/09/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 43413741</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1882459</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>flynn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pleeker</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 mccaughey street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flemington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4020</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/11/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6694734</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>jesurasingham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>249 national circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>como</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3106</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 16510100</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5361664</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ethan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>quilliam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 willoughby crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ryde</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2780</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/08/1961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 82444349</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8849090</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>robson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>30 balonne street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>maryborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2605</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/07/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93260323</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3104813</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hemmings</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 gaby place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>yetholme</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2030</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/05/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 97498075</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8375526</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bridget</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nutter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 o'connell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>anglesea</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4670</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/06/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 27437665</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6410520</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 tenison-woods circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dalby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3046</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/06/1943</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 14340503</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1634638</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jade</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tonge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>87 seaborn place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>blacktown</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4870</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/09/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 02491094</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9360231</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>polly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>32 bingley crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>stanley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6023</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/10/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1265966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mccarthy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>45 macgillivray street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glossop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2536</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/06/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 73212291</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6677779</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>petrinolis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 charlton crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>streatham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3141</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/11/1921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 22601453</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8718816</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sean</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hanna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 karney street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cherrybrook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2518</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/09/1998</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 39778110</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7967513</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>max</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>sneesby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>65 slessor crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>jerrabomberra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2800</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/08/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 73503838</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9005929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ziersch</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 brereton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dulwich hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3807</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/01/1979</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 27169607</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5769720</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>schuster</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 manuka circle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>park orchards</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3266</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 94271418</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4939777</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hope</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>47 zamia place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>condell park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4030</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 96055791</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2699952</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>toby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>denholm</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 duterrau crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hackham west</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2770</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/01/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 76918870</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3105162</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>elki</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wolfenden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 macgregor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>currambine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2325</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/10/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 43005670</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9692322</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 jemalong street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beenleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2026</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/03/1962</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 11898098</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4728630</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>andrew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 mcclure street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rosewater</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4078</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/03/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93839934</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7314912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>elana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lanyon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 ashburner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount riverview</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7331</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/07/1933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 52679372</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3412733</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ajay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>royle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>141 newman morris circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>safety bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4346</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/01/1921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 74647125</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4614145</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alice</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>youngs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1115 bushby place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rowville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4505</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/10/1962</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 25772485</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6016079</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chantha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 cripps place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>canley heights</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2036</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/10/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 91795012</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3328147</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>dylan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>paterson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>123 noble close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greenwith</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7290</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/01/1992</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 16775788</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7852858</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alysha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>miles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>103 carslaw street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leichhardt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6530</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/12/1947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 94822811</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7352254</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>eyles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>524 maxworthy street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wy yung</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5155</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/11/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 62946993</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2680727</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kouba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cowra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4113</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/07/1927</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 93947253</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8911379</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>natalie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mahon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 molesworth street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wyoming</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4127</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/03/1958</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 47957131</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8604509</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hambridge</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>56 holyman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moruya</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2370</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/11/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 00053933</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7977523</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aloysius</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chandler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>58 beattie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>carlingford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4152</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/10/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 44883940</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7459432</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stoker</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>129 finniss crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6062</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/11/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 20918941</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4760939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ebony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>meadows</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 powlett street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clayfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2257</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/10/1980</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5360301</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>max</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whaipe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>478 southern cross drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>springfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4108</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/11/1976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 21533136</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1944918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shaun</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>doman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>49 cromwell circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kings park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2099</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/06/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 84190605</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5637274</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jamie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ban</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>68 lambrigg street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>parramatta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6006</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/09/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 43612967</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9193120</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>bella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>51 canberra avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>figtree</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3166</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/05/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 68761349</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9592416</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>gilbertson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 bursaria street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>east maitland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3207</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/01/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 64228167</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2866098</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blandis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>41 skinner street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>loftus</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5017</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/04/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 91560966</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1377193</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>dimitri</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>braithwaite</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 slessor crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coober pedy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3042</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/04/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 60406243</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2315904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tiffany</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tiller</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>315 tunney crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>macleay island</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3101</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/05/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 54879641</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6931821</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jonah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>howlet</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 darby street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>modbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2518</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/04/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 24519577</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4589970</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>timothy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>41 torrens street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>findon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2800</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/08/1978</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 21442408</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9524864</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kieren</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>zimmermann</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 majura avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2262</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/10/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 30242856</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8270898</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>newey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 dalgarno close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>vermont</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3337</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/09/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 42317311</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8663048</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alicia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>huebner</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>37 lachlan street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>newstead</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3840</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/03/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 23357808</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6829295</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>crouch</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>84 stacy street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2866</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/12/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8302236</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cain</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>magazin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>57 duffy street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>newborough east</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5082</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/07/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 25752572</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4443961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>charlotte</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 bendigo street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>brighton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3015</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/07/1976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 31737635</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9447204</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>chloe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 batchelor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>chelsea heights</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2560</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/02/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 45161019</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3674735</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hugh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>eyles</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 starke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>maiden gully</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3172</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/06/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 61003533</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7695496</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>elysse</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>trimble</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 nemarang crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clayton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4228</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/01/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 98354307</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1297767</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>murton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>68 barrallier street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3172</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/06/1955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 47457480</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6776470</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wang</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 monaghan place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingsthorpe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6210</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/11/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6335437</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alicia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>alderson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 fornax street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>anula</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2018</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1941</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 31091875</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8860745</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stephanie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wastell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>123 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>loganholme</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4563</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/10/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 30684061</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6962877</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shanaye</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>henshaw</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 ross smith crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lewiston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4127</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/07/1924</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5326326</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harrison</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>neander</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>45 weddin circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>norwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2151</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/10/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 46635552</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6488863</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>crossman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 babbage crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>geelong west</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3103</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/07/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 15559019</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1945932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mackinley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>marris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>113 browne place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broome</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2480</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/04/1939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 54591849</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4394861</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lachlan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcphail</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 hyndes crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>stoneville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2126</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/11/1901</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 21796253</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9568098</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>anastasia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>padas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>65 percy crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>port pirie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4218</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 70666043</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2594128</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ebony</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cochrane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 glyde place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>warrandyte</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4812</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/06/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 98641894</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8317923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lachlan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pyle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 harbison crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kimba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3060</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/02/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 43108138</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+            <!-- Return to home page -->
+            <tr>
+              <td>clickAndWait</td>
+              <td>logout</td>
+              <td> </td>
+            </tr>
+    
diff --git a/Selenium/CollectionTest3d.html b/Selenium/CollectionTest3d.html
new file mode 100644
index 0000000..9066240
--- /dev/null
+++ b/Selenium/CollectionTest3d.html
@@ -0,0 +1,19333 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 3d - load synthetic cases</title>
+  <!-- Loads a few hundred synthesised cases. The names and addresses were
+       synthesised using the dbgen utility from the Febrl v0.3 open source
+       probabilistic record linkage suite, which is available from
+       http://febrl.sourceforge.net Any resemblance of the names and addresses
+       in this test to real persons is entirely coincidental.-->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 3d - load synthetic cases<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8265079</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>deroos</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 macnamara place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hawthorne</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4076</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/09/1921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 00561586</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2326939</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>154 booroomba road</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bray park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2220</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/05/1950</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 15629965</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8643417</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>georgia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>egan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 kurria place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3073</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/05/1974</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 83878305</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3732912</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>noah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wurm</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>142 leisler close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mentone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4558</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/04/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2010598</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kirkmoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 totterdell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>brookfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2256</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/12/1904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 55406269</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1351182</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kierra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 dobbie place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>four mile creek</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2540</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/11/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 83565723</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4475825</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>holly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rollins</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>351 faunce crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>campbellfield</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>0870</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/12/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 04493162</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5043680</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>abbey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>645 lorne place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>auburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5172</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/04/1936</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 80627549</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7685976</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>claudia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>widdowson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 pomeroy street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>invermay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5501</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/10/1977</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 29593165</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5029485</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tsermentselis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>43 arthur circle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hope valley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3028</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 65771911</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3988818</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bail</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 mctaggart crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mill park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2036</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/08/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1289773</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>snell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>63 somerville street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>paddington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6714</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/03/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 65010107</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5985440</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matilda</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pichugin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>949 warabin crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bundaberg</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2318</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/05/1948</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 64692057</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6936191</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brooke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>van balen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ruse</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4077</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/02/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 38094948</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3364066</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>liam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>truman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>48 bennett street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>annandale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4220</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/04/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 22996297</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7433947</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>timothy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blows</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 teague street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>upwey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3224</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/08/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 04467096</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1919290</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>george</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>479 jenner court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wanniassa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4114</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/11/1981</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 14612568</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2647105</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>passmore</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 english court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>annandale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6244</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/11/1929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 98076621</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5897473</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>scott</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>paine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> eggleston crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>milang</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2121</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/01/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 04694932</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8157227</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>irene</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>marshman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 jalanga crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>indented head</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4171</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/08/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 08547064</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7711269</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>daniel</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>jolly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 summerville crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mount ossa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2518</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/09/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 03644182</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7374983</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jordan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>7 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gowrie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4213</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/11/1916</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2375164</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>cavanaugh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 cowper street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dayboro</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4061</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/05/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 97878903</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8931499</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>450 lucy gullett circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>floraville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4370</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/10/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 57162296</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8542686</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aleisha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>demarco</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>55 coglin place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>auburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4370</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/12/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3129324</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>madeleine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bruse</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 haddon street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cabramatta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Northern Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/10/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 67984098</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6874866</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>maxin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>vincent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 bingley crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>goodwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3860</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/08/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 43870068</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3103603</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>james</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mostris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 white crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glenmore park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3130</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/09/1909</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 62129920</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3959386</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matthew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wang</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 springvale drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>currans hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4560</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/11/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 16254815</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4946583</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nikki</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pyper</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>18 gellatly place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greenfield park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4127</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/01/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 00606486</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3045903</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hope</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>91 joyner place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2321</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/03/1955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 57908401</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1966630</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ajay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>felmingham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 de burgh street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gilmore</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3175</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/01/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 51197513</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2562792</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tootell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 outtrim avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clifton springs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2120</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/11/1954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 61378161</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3765361</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>genevieve</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>callahan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 kellick place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>lower templestowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5086</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/07/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 53646540</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2113500</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sian</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coolahan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 bokhara circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>east fremantle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5082</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/10/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 45301429</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5804054</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>trinity</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>frauenfelder</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 moonta place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>bonnyrigg</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3187</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/08/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 48222588</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2686701</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bishop</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>125 upton street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2031</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/07/1957</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 89476116</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6510021</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>amy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>panigiris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 bussell crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>toowoomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3806</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/11/1983</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 09930717</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6467483</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ajay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>herbert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>71 lyster place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>fullarton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4507</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/01/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 23207084</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2911987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>katie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whiteley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 tunney crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>pennant hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3350</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/04/1933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 90662453</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9741345</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>corie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>moody</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>208 dalabon crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ashgrove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3995</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/11/1959</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 88818719</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6732920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hannah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>grillett</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 amagula avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moorooka</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2906</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/03/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 99533191</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7872895</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 sinclair street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>seaview downs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5211</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 54692630</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5884161</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hazell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 sherbrooke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>preston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4102</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/08/1967</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 17572356</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5496612</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caleb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>127 kalgoorlie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3156</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/06/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9640129</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>julia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 kingscote crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>quambatook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5223</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/01/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 40522786</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9422597</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>noah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bristow</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>67 lawrence wackett crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coffs harbour</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3073</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/02/1910</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 92766233</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1311606</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>joshua</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>grosser</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>189 vancouver street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>parkside</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3197</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Northern Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/08/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4281375</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>flynn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dolby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 florence taylor street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glenwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2602</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/08/1963</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 23995104</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1519949</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alisha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>steers</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 maltby circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>labrador</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3215</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/07/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 01214713</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7429648</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>alysha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stephenson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 blackwell circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>currambine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3856</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/01/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 96849782</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9496876</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>paige</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>idzikowski</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 woollum place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>pooraka</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6149</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/09/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 01140791</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1023656</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>erin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 liffey circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kedron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2720</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/07/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 42343040</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1789482</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 bosisto place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>north ryde</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5076</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/05/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 93852900</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7324742</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>holly</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>joergensen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 miller street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beaumaris</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2161</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/01/1938</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 03065702</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1711995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>cain</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tomney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sunshine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3918</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/05/1944</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 43304892</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7711161</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ollivier</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 waddell place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kogarah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2232</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/02/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 84319930</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4079432</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brigette</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>71 barton highway</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>edithvale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4035</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/02/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 02251467</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1033012</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jasmine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>curry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>105 mellor circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clifton grove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5341</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/04/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 65932848</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8610430</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jayde</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 foxall street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greensborough</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6031</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/02/1922</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 34237379</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3045636</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>claudia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>alderson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 callaway crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>elermore vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7249</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/09/1915</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 44083853</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8179596</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>grace</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 pitcairn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>park orchards</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>15/05/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 04076643</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3345666</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>erin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>allsopp</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 laurie place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>orange</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6163</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/05/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 76104036</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8819035</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>arabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>75 burkitt street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>success</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2460</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/11/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 28357361</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2055350</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> payne place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>auburn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2042</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/06/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 80210554</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4174053</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>christopher</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>10 thomson street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>moorabbin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5084</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/02/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 46057996</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5712429</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lamprey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>67 lexcen avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sunnybank</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2474</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/04/1936</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 67595273</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4344354</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>peter</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>satterley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>40 mueller street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>coomba</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5082</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/06/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 80156533</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5119954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caleb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lizzi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 blackett crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>flinders park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7010</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>07/04/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 36432778</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2378164</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>liam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>crook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>38 goodwin street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>new beith</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2765</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/05/1924</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 57971601</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8962048</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>31 battersby circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>pennant hills</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5037</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/09/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 36887457</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3417060</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lucas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 marcus clarke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>thornlie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3212</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/10/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 63359925</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8034171</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>caitlin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tinsley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 crisp circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glenbrook</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3011</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/10/1951</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 77695188</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7477738</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>etherington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>389 bowling place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>homebush</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3207</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/10/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 75325620</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2105707</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>edward</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>orme</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>67 goodwin street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>chinchilla</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4680</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/07/1989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9182815</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>rebekah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>lowe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>416 sturdee crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>midvale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3380</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/04/1984</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 00589349</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8569533</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>connor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 rosenthal street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wentworth falls</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4872</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/04/1964</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2369672</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>macormack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>goldsworthy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 thurgood court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>condell park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4700</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/02/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 26099805</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1745101</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>390 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>parkdale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2077</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/09/1911</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 63902975</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9022323</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>robert</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>green</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>85 la perouse street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>caulfield north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3107</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/04/1905</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 53760532</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8190053</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>elana</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>clarke</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>73 wootton crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ivanhoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2478</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/06/1987</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 83629398</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9197184</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>rory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 de little circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>colo vale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2033</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/01/1935</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 01352254</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1266432</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lucy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hope</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>184 lorne place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>westmead</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4215</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/10/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 53534147</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7782124</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>latham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 freda gibson circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>albury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2287</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 00684988</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5359572</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>darcy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>meaney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 henty street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>burwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3350</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/11/1990</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 42080741</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3394040</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jake</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>whisson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 yolla place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>greenwood</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6147</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/04/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 30282078</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3330141</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ellouise</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blackney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 whitford place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>pallamallawa</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2194</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/05/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 64677795</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5504804</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rawlings</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>43 keira street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ventnor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4559</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/09/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 14935066</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9294959</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>adam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>maynard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>62 grounds crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>safety bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2154</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/01/1931</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8155081</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>schkirra</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pascoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>103 baddeley crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kings park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2481</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/10/1980</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 26756836</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3132109</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>walls</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 gallagher street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>normanhurst</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2500</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/08/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 62068378</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6216075</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>abby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>burford</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 mina wylie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>new town</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3178</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/04/1955</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 58149333</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5077917</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>125 philp place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>wyong</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2795</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1932</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 76713329</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5660667</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>clement</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>thorpe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 nemarang crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>cudal</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3370</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/09/1978</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 27104846</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2880565</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>roberta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>rees</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>6 embling street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ballina</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2330</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>03/06/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3367954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>reid</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 julia flynn avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gaven</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4565</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/09/1961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 09769297</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1916740</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>hayden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>michelmore</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>82 jansz crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kedron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3143</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/11/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 30195920</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3171568</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>toby</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>boss</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 hunter street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>corio</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4818</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/06/1972</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 85422803</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3568937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>kiara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mposu</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 lofty close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>joondalup</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6532</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/06/1929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 00399044</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1436068</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aiden</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wilkins</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>19 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>berwick</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2032</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/12/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 95563399</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8135394</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>pascale</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>liddy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td> doyle terrace</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>minto</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4300</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/07/1966</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 12939147</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1482407</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>66 forbes street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ivanhoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6059</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/08/1961</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 91520859</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5391647</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>harry</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>manson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 russell drysdale crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clifton springs</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2287</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/05/1940</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 38502077</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4140928</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>dylan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pascoe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>107 la perouse street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>brisbane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4356</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/01/1980</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 73586712</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7757991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jessica</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>127 pandanus street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>port macquarie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3149</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/02/1975</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 95510781</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5464604</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>gabrielle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>redmond</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>42 golden grove</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sandy bay</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6062</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/10/1960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 59566124</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9370435</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>andrew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>musolino</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>59 mackay crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dulwich hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5085</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/02/1953</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 71407158</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4655739</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tregoning</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 totterdell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>binningup</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6019</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/03/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 32259960</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1434799</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>156 totterdell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>windsor</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4510</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/03/1946</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 07754785</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5338290</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>penm</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>96 anne clark avenue</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>deagon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5165</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/10/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 77529088</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6322331</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>oliver</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>verner</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 pasley place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>doubleview</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6163</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/10/1913</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 78284517</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2493465</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>campbell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 perry drive</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rosebery</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3820</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>26/05/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 00729553</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6681890</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>allard</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>24 lipscomb place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>campsie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5302</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/12/1988</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 74912406</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4067482</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>timothy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>tippins</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>3 sherbrooke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>primrose sands</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6516</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/01/1996</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 89965343</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1315542</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zac</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>laing</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>82 hibiscus crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>parkville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3055</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 86761648</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5902921</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brooklyn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>12 james street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>glenning valley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7214</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>13/03/1919</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 93130181</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5618894</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>andrew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dixon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>66 kellermann close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>vista</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2060</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/02/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 21836006</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2571811</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>koula</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>magerl</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 waugh close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>medina</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3186</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/10/1968</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 58850288</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2224986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>phoebe</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>fromentin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>26 marcus clarke street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>ballarat</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2756</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>17/06/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 38060685</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8323021</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>marlow</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>41 longmore crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>newstead</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6210</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>16/08/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 09514802</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1031763</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mitchell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>14 youl court</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>gunbower</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2324</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>27/11/1985</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 22213728</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8576970</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jordan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pierro</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 derrington crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broken hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3199</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>18/11/1914</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 87613131</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9427806</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>domenique</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>block</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>25 cygnet crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>granville</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6030</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/11/1956</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 10385405</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2227787</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>olivia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>nguyen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>36 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kedron</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4123</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/09/1993</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 15268553</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7405671</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>david</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>price-austin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>28 totterdell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>padstow</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5095</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/01/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 31855919</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7545794</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>brinley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>17 hopetoun circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4701</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>24/12/1928</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 26778508</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1404671</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kannangara</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>265 torrens street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>marayong</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2479</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/09/1923</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 19090552</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2815438</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>coleman</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>4 lind close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>beeliar</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6108</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/04/1989</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 97775731</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6678503</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sylvie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mac neill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>1 parkhill street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>irymple</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4575</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/01/1934</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 89580998</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9698752</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>tynan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>david</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>44 custance street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mossy point</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4223</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>14/04/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 11144973</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1421548</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>william</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>121 salsola street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>surfers paradise</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6156</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Australian Capital Territory</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>22/05/1959</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5686342</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sam</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>bennier</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 hurley street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>clayton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>7007</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/08/1907</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 77915625</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5789738</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>montanna</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>kassoudakis</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>54 glynn street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>seaton</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6053</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/10/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 52440767</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4382394</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>levi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>birthwhistle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>2 nassau place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hawthorn</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2162</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>09/06/1997</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3389176</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>riley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>chandler</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>83 lyttleton crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>taree</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td></td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/11/1903</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 07944457</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9588033</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>louise</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>millanta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>23 brookman street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mackay north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2293</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Tasmania</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>12/10/1954</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 62741820</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8654962</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>benjamin</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>heyes</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 anzac park</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>dapto</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4017</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>30/04/1918</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 23013217</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>3553887</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>mia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>15 fairweather circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>raymond island</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3170</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/04/1917</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td></td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7812959</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hand</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 decker place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rosebud</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3741</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>South Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>19/10/1999</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 85278772</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4826732</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>shakira</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>waterston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 oakover circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>leichhardt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2102</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>21/05/1920</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 72948897</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5660475</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>ella</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>paterson</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>141 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mentone</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3044</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>05/04/1965</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 31608564</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5928050</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>nicholas</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>stanley</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>11 gratwick street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sunshine</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3936</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/06/1982</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 97882033</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8088960</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jacinta</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>webb</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>117 coranderrk street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>mitcham</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3103</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>25/04/1969</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 57955939</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7279611</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>sophie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>blackwell</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>27 wilkins street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>rochedale south</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5117</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/01/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 03761216</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>5359026</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>emiily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>wasser</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>22 snowy place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broken hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3108</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>20/04/1900</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 52180887</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1381661</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>stelio</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>harrington</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>152 minchin place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>padstow</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6003</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/08/1995</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 40669488</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4500661</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>lily</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>matthews</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 bennetts close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>allawah</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5252</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Western Australia</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>11/02/1971</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 83131013</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>8269834</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jock</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>duus</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>21 moloney close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>frankston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>6442</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/10/1936</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 19211027</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9565593</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>dillon</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>hurl</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>20 allman circuit</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingston</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2250</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>08/01/1925</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 23145941</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9650612</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>samantha</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>dearing</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>81 carnegie crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>thornbury</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2223</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>04/03/1928</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 58610023</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7343307</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>casey</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>shepherd</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>13 petre street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>broken hill</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3012</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/10/1933</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 84562565</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>2255241</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>isabelle</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>mcmullen</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>9 derrington crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>kingaroy</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2074</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>01/05/1908</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 74534690</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>9350615</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>courtney</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>trninic</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>5 strickland crescent</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>canyonleigh</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2065</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>23/03/1937</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 65748251</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4132525</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>jack</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>commons</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>191 dalyell street</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>banyo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2000</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>06/05/1929</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 89880564</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4634308</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>andrew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>castelluzzo</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>90 pryton close</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>port macquarie</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2035</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>02/11/1986</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>02 15604323</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>6922360</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>matthew</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>ryan</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>8 </td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>eumundi</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>2232</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>10/08/1991</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>03 23105578</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>7484877</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>zane</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>oliphamt</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>49 mugga way</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>hahndorf</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>4615</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>New South Wales</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>28/10/1904</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>07 17421445</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>1519564</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>aikaterina</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>white</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>33 mirams place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>sunshine north</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>3030</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Queensland</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>31/12/1973</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>08 57827819</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>4064542</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>beau</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>pasalic</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>16 mullens place</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>trafalgar</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>5600</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>Victoria</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>29/11/1906</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>04 85272527</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    
+
+            <!-- Return to home page -->
+            <tr>
+              <td>clickAndWait</td>
+              <td>logout</td>
+              <td> </td>
+            </tr>
+    
diff --git a/Selenium/TestSuite.html b/Selenium/TestSuite.html
new file mode 100644
index 0000000..8a43266
--- /dev/null
+++ b/Selenium/TestSuite.html
@@ -0,0 +1,43 @@
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+<meta content="text/html; charset=ISO-8859-1"
+http-equiv="content-type">
+<title>NetEpi Collection Test Suite</title>
+
+</head>
+
+<body>
+
+<table     cellpadding="1"
+           cellspacing="1"
+           border="1">
+        <tbody>
+            <tr><td><b>NetEpi Collection Test Suite</b></td></tr>
+            <tr><td><a href="./CollectionTest1.html">CollectionTest1</a></td></tr>
+            <tr><td><a href="./CollectionTest2.html">CollectionTest2</a></td></tr>
+            <tr><td><a href="./CollectionTest3.html">CollectionTest3</a></td></tr>
+        </tbody>
+    </table>
+
+</body>
+</html>
diff --git a/Selenium/make_CollectionTest3.py b/Selenium/make_CollectionTest3.py
new file mode 100644
index 0000000..f2a5a90
--- /dev/null
+++ b/Selenium/make_CollectionTest3.py
@@ -0,0 +1,276 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import csv
+
+test_head = """
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004, 2005 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+<head>
+  <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+  <title>NetEpi Collection Test 3%s - load synthetic cases</title>
+  <!-- Loads a few hundred synthesised cases. The names and addresses were
+       synthesised using the dbgen utility from the Febrl v0.3 open source
+       probabilistic record linkage suite, which is available from
+       http://febrl.sourceforge.net Any resemblance of the names and addresses
+       in this test to real persons is entirely coincidental.-->
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+  <tbody>
+    <tr>
+      <td rowspan="1" colspan="3">NetEpi Collection Test 3%s - load synthetic cases<br>
+      </td>
+    </tr>
+    <!-- Test login and logout -->
+    <tr>
+      <td>open</td>
+      <td>../../cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyLocation</td>
+      <td>/cgi-bin/casemgr/app.py</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>NetEpi Collection<br>
+      </td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>user</td>
+      <td>admin</td>
+    </tr>
+    <tr>
+      <td>type</td>
+      <td>password</td>
+      <td>sn00Py</td>
+    </tr>
+    <tr>
+      <td>clickAndWait</td>
+      <td>login</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>Welcome Administrator</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>You are a member of the Administrator Unit.</td>
+      <td> </td>
+    </tr>
+    <tr>
+      <td>verifyTextPresent</td>
+      <td>SARS</td>
+      <td> </td>
+    </tr>
+"""
+
+offsets_dict = {'a':0,'b':250,'c':500,'d':750}
+
+for series in ['a','b','c','d']:
+
+    # Use the dbgen utility from Febrl (see http://febrl.sourceforge.net)
+    # to generate a suitable syntheised dataset.
+    data = open('CollectionTest3_data.csv','rb')
+
+    reader = csv.reader(data)
+    rownum = -1
+
+    testfile = open('CollectionTest3%s.html' % series,'w')
+
+    print >> testfile, test_head % (series, series)
+
+    template = """
+        <!-- Add a new case of SARS -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>new:2</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Add Case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Please search to make sure the person you are going to add is not already in the database.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>do_search</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Search Results</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>clickAndWait</td>
+          <td>new_case</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Local Case ID</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset Date</td>
+          <td> </td>
+        </tr>
+        <!-- Fill in the details of the new case -->
+        <tr>
+          <td>type</td>
+          <td>local_case_id</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>given_names</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>surname</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>street_address</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>locality</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>postcode</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>select</td>
+          <td>state</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>DOB</td>
+          <td>%s</td>
+        </tr>
+        <tr>
+          <td>type</td>
+          <td>home_phone</td>
+          <td>%s</td>
+        </tr>
+        <!-- Save the data and verify -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>update</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Onset symptoms (SARS)</td>
+          <td> </td>
+        </tr>
+        <!-- Return to home page -->
+        <tr>
+          <td>clickAndWait</td>
+          <td>home</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>Welcome Administrator</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>You are a member of the Administrator Unit.</td>
+          <td> </td>
+        </tr>
+        <tr>
+          <td>verifyTextPresent</td>
+          <td>SARS</td>
+          <td> </td>
+        </tr>
+    """
+
+    statesdict = {'NSW':'New South Wales','VIC':'Victoria',
+                  'QLD':'Queensland', 'ACT':'Australian Capital Territory',
+                  'NT':'Northern Territory', 'SA':'South Australia',
+                  'TAS':'Tasmania', 'WA':'Western Australia','':''}
+
+    offset = offsets_dict[series]
+    print series, offset
+    
+    for row in reader:
+        rownum += 1
+        if rownum > (0 + offset) and rownum < (201 + offset):
+            params = (row[12].strip(), row[1].strip(), row[2].strip(), row[3].strip() + ' ' + row[4].strip(),
+                     row[6].strip(), row[7].strip(), statesdict[row[8].upper().strip()],
+                     str(row[9])[7:9].strip() + '/' + str(row[9])[5:7].strip() + '/' + str(row[9])[1:5].strip(),
+                     row[11].strip())
+            if params[-2] != '//' and params[-3] != '' and params[2] != '':
+                print >> testfile, template % params
+
+    footer = """
+            <!-- Return to home page -->
+            <tr>
+              <td>clickAndWait</td>
+              <td>logout</td>
+              <td> </td>
+            </tr>
+    """
+
+    print >> testfile, footer
+    testfile.close()
+
+    data.close()
+
diff --git a/Trac_licence.txt b/Trac_licence.txt
new file mode 100644
index 0000000..e30ac52
--- /dev/null
+++ b/Trac_licence.txt
@@ -0,0 +1,32 @@
+Trac License �
+Trac is licensed under the following modified BSD license. 
+
+Copyright (C) 2003-2006 Edgewall Software
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in
+   the documentation and/or other materials provided with the
+   distribution.
+3. The name of the author may not be used to endorse or promote
+   products derived from this software without specific prior
+   written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/app/admin.js b/app/admin.js
new file mode 100644
index 0000000..8c45a6b
--- /dev/null
+++ b/app/admin.js
@@ -0,0 +1,470 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others. 
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+
+/* depends on helpers.js */
+
+/*global window 
+    absNodePos addClass addEvent appendAfter cancelEvent cancelSelection
+    classApply clickOn delEvent distance elementHasClass mouseIsNear parentTag
+    rmClass syntheticSubmit viewTopLeft */
+
+var jsselect = function (topNodeName, selectCallback, clickCallback, 
+                         pathSnapping) {
+    var topNode = document.getElementById(topNodeName),
+        selectableMap = {},
+        selectableOrder = [],
+        startSelection, endSelection,
+        initialX, initialY, initialNode,
+        floater = document.createElement('div');
+    var box = function (a, b) {
+        // Adjust size of "floater" to enclose nodes "a" and "b"
+        var aPos = absNodePos(a),
+            bPos = absNodePos(b),
+            x1 = Math.min(aPos.x, bPos.x),
+            y1 = Math.min(aPos.y, bPos.y),
+            x2 = Math.max(aPos.x + a.clientWidth, bPos.x + b.clientWidth),
+            y2 = Math.max(aPos.y + a.clientHeight, bPos.y + b.clientHeight);
+        floater.style.left = x1 + 'px';
+        floater.style.top = y1 + 'px';
+        floater.style.width = (x2 - x1) + 'px';
+        floater.style.height = (y2 - y1) + 'px';
+    };
+    var findSelectable = function (node) {
+        // Recurse back up the tree looking for a "selectable" node, stopping
+        // if we see a node with "noselect" in CSS class, or hit top.
+        while (node) {
+            if (elementHasClass(node, 'noselect')) {
+                return null;
+            }
+            if (node.id && node.id in selectableMap) {
+                break;
+            }
+            node = node.parentNode;
+        }
+        return node;
+    };
+    var pathSnap = function (includeChildren) {
+        // If selectable identifiers are in a heirachy, ensure selection only
+        // selects nodes at the same level.
+        var i = Math.min(startSelection, endSelection),
+            ll = Math.max(startSelection, endSelection),
+            selected = [], prefixes = {},
+            minlen = 999, node, nodeName;
+        for (;i <= ll; i++) {
+            node = selectableOrder[i];
+            selected.push(node.id);
+            if (node.id.length < minlen) {
+                minlen = node.id.length;
+            }
+        }
+        for (i = 0, ll = selected.length; i < ll; ++i) {
+            prefixes[selected[i].substr(0, minlen)] = undefined;
+        }
+        selected = [];
+        for (i = 0, ll = selectableOrder.length; i < ll; ++i) {
+            node = selectableOrder[i];
+            nodeName = includeChildren ? node.id.substr(0, minlen) : node.id;
+            if (node.id && nodeName in prefixes) {
+                selected.push(node);
+            }
+        }
+        return selected;
+    };
+    var getSelection = function () {
+        var lower, upper;
+        if (pathSnapping) {
+            return pathSnap();
+        } else {
+            lower = Math.min(startSelection, endSelection);
+            upper = Math.max(startSelection, endSelection);
+            return selectableOrder.slice(lower, upper + 1);
+        }
+    };
+    var clearSelection = function () {
+        startSelection = endSelection = undefined;
+        initialX = initialY = initialNode = undefined;
+    };
+    var clearHighlight = function () {
+        document.body.removeChild(floater);
+    };
+    var scrollIfNeeded = function (mouseY) {
+        var threshold = 50,
+            viewTop = window.scrollY || document.documentElement.scrollTop,
+            viewLeft = window.scrollX || document.documentElement.scrollLeft,
+            viewHeight = (window.innerHeight || 
+                                  document.documentElement.clientHeight);
+        if (mouseY < threshold) {
+            window.scrollTo(viewLeft, viewTop - threshold / 2);
+        } else if (mouseY > viewHeight - threshold) {
+            window.scrollTo(viewLeft, viewTop + threshold / 2);
+        }
+    };
+    var doMouseMove = function (e) {
+        var ev = e || event,
+            target = ev.target || ev.srcElement,
+            selected;
+        if (startSelection === undefined) {
+            if (distance(ev.screenX, ev.screenY, initialX, initialY) < 20) {
+                return true;
+            }
+            startSelection = selectableMap[findSelectable(initialNode).id];
+            document.body.insertBefore(floater, document.body.firstChild);
+        }
+        cancelSelection();
+        target = findSelectable(target);
+        if (!target) {
+            return true;
+        }
+        endSelection = selectableMap[target.id];
+        scrollIfNeeded(ev.clientY);
+        if (pathSnapping) {
+            selected = pathSnap(true);
+            box(selected[0], selected[selected.length - 1]);
+        } else {
+            box(selectableOrder[startSelection], target);
+        }
+    };
+    var doMouseUp = function (e) {
+        var ev = e || event,
+            selected;
+        delEvent(document.body, 'mouseup', doMouseUp);
+        delEvent(document.body, 'mousemove', doMouseMove);
+        if (startSelection === undefined) {
+            if (clickCallback) {
+                clickCallback(initialNode, ev);
+            } else {
+                clickOn(initialNode);
+            }
+        } else {
+            if (selectCallback) {
+                selected = getSelection();
+                clearSelection();
+                selectCallback(selected, clearHighlight, ev);
+            } else {
+                clearSelection();
+                clearHighlight();
+            }
+        }
+    };
+    var doMouseDown = function (e) {
+        var ev = e || event,
+            target = ev.target || ev.srcElement,
+            is_oldIE = !ev.preventDefault;
+        if ((is_oldIE && ev.button !== 1) ||
+            (!is_oldIE && ev.button !== 0)) {
+            return;
+        }
+        if (!findSelectable(target)) {
+            return true;
+        }
+        initialX = ev.screenX;
+        initialY = ev.screenY;
+        initialNode = target;
+        addEvent(document.body, 'mouseup', doMouseUp);
+        addEvent(document.body, 'mousemove', doMouseMove);
+        return cancelEvent(ev);
+    };
+    floater.style.position = 'absolute';
+    floater.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(Opacity=30)';
+    floater.style.opacity = '0.3';
+    floater.style.zIndex = -1;
+    floater.className = 'selector';
+    classApply(topNode, 'selectable', 
+        function (node) {
+            if (node.id) {
+                selectableMap[node.id] = selectableOrder.length;
+                selectableOrder.push(node);
+                node.style.cursor = 'pointer';
+                addEvent(node, 'mousedown', doMouseDown);
+                addEvent(node, 'selectstart', function () { 
+                    return false;
+                });
+            }
+        });
+};
+
+var fe_click = function (node) {
+    while (node && !node.id) {
+        node = node.parentNode;
+    }
+    if (node) {
+        syntheticSubmit('appform', 'edit', node.id);
+    }
+};
+
+var fe_select = function (formName, selectField, actions) {
+    var fe_select_handler = function (selectedNodes, clearSelection, ev) {
+        var i, ll, node, kill,
+            nodeNames = [],
+            div = document.createElement('div'),
+            form = document.forms[formName],
+            viewTop = window.scrollY || document.documentElement.scrollTop,
+            viewLeft = window.scrollX || document.documentElement.scrollLeft;
+        var detectOut = function (e) {
+            var ev = e || event,
+                target = ev.target || ev.srcElement;
+            while (target) {
+                if (target === div) {
+                    return;
+                }
+                target = target.parentNode;
+            }
+            kill();
+        };
+        kill = function () {
+            if (div) {
+                delEvent(document.body, 'mousemove', detectOut);
+                form.removeChild(div);
+                div = undefined;
+                clearSelection();
+            }
+        };
+        var cancel = function (e) {
+            var ev = e || event;
+            kill();
+            cancelEvent(ev);
+        };
+        div.style.position = 'absolute';
+        div.style.width = '6em';
+        div.style.background = 'white';
+        div.style.zIndex = '1000';
+        div.style.top = (viewTop + ev.clientY - 10) + 'px';
+        div.style.left = (viewLeft + ev.clientX - 10) + 'px';
+        for (i = 0, ll = actions.length; i < ll; ++i) {
+            node = document.createElement('input');
+            node.type = 'submit';
+            node.name = actions[i].toLowerCase();
+            node.value = actions[i];
+            node.style.width = '100%';
+            node.style.margin = '0';
+            node.className = "butt";
+            div.appendChild(node);
+            if (node.name === 'cancel') {
+                addEvent(node, 'click', cancel);
+            }
+        }
+        form.insertBefore(div, form.firstChild);
+        addEvent(document.body, 'mousemove', detectOut);
+        for (i = 0, ll = selectedNodes.length; i < ll; ++i) {
+            node = selectedNodes[i];
+            nodeNames.push(node.id);
+        }
+        form[selectField].value = nodeNames.join(',');
+    };
+    return fe_select_handler;
+};
+
+var expandOnHover = function (container, opener, openee) {
+    var close = function () {
+        opener.style.display = 'inline';
+        openee.style.display = 'none';
+    };
+    var moveClose = function (e) {
+        var ev = e || event;
+        if (!mouseIsNear(container, ev)) {
+            delEvent(document.body, 'mousemove', moveClose);
+            close();
+        }
+    };
+    close();
+    addEvent(opener, 'mouseover', function () {
+        opener.style.display = 'none';
+        openee.style.display = 'inline';
+        addEvent(document.body, 'mousemove', moveClose);
+    });
+};
+
+var attachExpandOnHover = function (topNodeId) {
+    classApply(document.getElementById(topNodeId), 'hover-expand', 
+        function (node) {
+            var i, ll, child, opener, openee;
+            for (i = 0, ll = node.childNodes.length; i < ll; ++i) {
+                child = node.childNodes[i];
+                if (elementHasClass(child, 'he-opener')) {
+                    opener = child;
+                }
+                if (elementHasClass(child, 'he-open')) {
+                    openee = child;
+                }
+            }
+            if (opener && openee) {
+                expandOnHover(node, opener, openee);
+            }
+        });
+};
+
+var attachClickOpenOne = function (topNodeId, formName, selectField) {
+    var selected;
+    var open = function (select) {
+        var selectNode, foldNode;
+        selectNode = document.getElementById('select:' + select);
+        foldNode = document.getElementById('fold:' + select);
+        if (!selectNode && !foldNode) {
+            alert(select + ' fold or select not found');
+            return;
+        }
+        addClass(selectNode, 'active');
+        foldNode.style.display = 'block';
+        if (selected && select !== selected) {
+            selectNode = document.getElementById('select:' + selected);
+            foldNode = document.getElementById('fold:' + selected);
+            rmClass(selectNode, 'active');
+            foldNode.style.display = 'none';
+        }
+        selected = select;
+        if (formName && selectField) {
+            document.forms[formName][selectField].value = select; 
+        }
+    };
+    var scan = function (node) {
+        var i, ll, fields, fold;
+        if (node.id) {
+            fields = node.id.split(':');
+            if (fields[0] === 'select') {
+                fold = document.getElementById('fold:' + fields[1]);
+                if (fold) {
+                    fold.style.display = 'none';
+                    node.style.cursor = 'pointer';
+                    addEvent(node, 'click', function () {
+                        open(fields[1]);
+                    });
+                }
+            }
+        }
+        for (i = 0, ll = node.childNodes.length; i < ll; ++i) {
+            scan(node.childNodes[i]);
+        }
+    };
+    scan(document.getElementById(topNodeId));
+    if (formName && selectField) {
+        open(document.forms[formName][selectField].value);
+    }
+};
+
+var rowMover = function (tableName, orderField) {
+    var idMap = {}, nMoveableRows = 0,
+        theTable = document.getElementById(tableName);
+    var startMove = function (targetRow, mouseX, mouseY) {
+        var dTable, offsetX, offsetY;
+        var detachedRowClone = function (tr) {
+            var table = tr.offsetParent,
+                dTable = table.cloneNode(false),
+                dBody = tr.parentNode.cloneNode(false),
+                dRow = tr.cloneNode(true),
+                padding = 1,
+                i, ll;
+            dTable.removeAttribute("id");
+            dTable.style.position = "absolute";
+            dBody.appendChild(dRow);
+            dTable.appendChild(dBody);
+            for (i = 0, ll = tr.cells.length; i < ll; ++i) {
+                var cell = tr.cells[i];
+                dRow.cells[i].width = (cell.clientWidth - 2 * padding) + "px";
+            }
+            return dTable;
+        };
+        var nearestRow = function () {
+            var i, ll, row, rowPos,
+                detachedPos = absNodePos(dTable.rows[0]);
+            for (i = 0, ll = theTable.rows.length; i < ll; ++i) {
+                row = theTable.rows[i];
+                rowPos = absNodePos(row);
+                if (row.id && 
+                        distance(rowPos.x, rowPos.y, 
+                                 detachedPos.x, detachedPos.y) < 20) {
+                    return row;
+                }
+            }
+        };
+        var moveDetached = function (x, y) {
+            var tBody, landingRow;
+            dTable.style.left = (x - offsetX) + "px";
+            dTable.style.top = (y - offsetY) + "px";
+            landingRow = nearestRow();
+            if (landingRow && landingRow !== targetRow) {
+                tBody = targetRow.parentNode;
+                if (targetRow.sectionRowIndex < landingRow.sectionRowIndex) {
+                    tBody.removeChild(targetRow);
+                    appendAfter(targetRow, landingRow);
+                } else {
+                    tBody.removeChild(targetRow);
+                    tBody.insertBefore(targetRow, landingRow);
+                }
+            }
+        };
+        var mouseMove = function (e) {
+            var ev = e || event,
+                view = viewTopLeft();
+            cancelSelection();
+            moveDetached(view.left + ev.screenX, view.top + ev.screenY);
+        };
+        var setOrderField = function () {
+            var i, ll, row, fields, ids = []; 
+            if (orderField) {
+                for (i = 0, ll = theTable.rows.length; i < ll; ++i) {
+                    row = theTable.rows[i];
+                    if (row.id && row.id in idMap) {
+                        ids.push(idMap[row.id]);
+                    }
+                }
+                fields = document.getElementsByName(orderField);
+                for (i = 0, ll = fields.length; i < ll; ++i) {
+                    fields[i].value = ids.join(',');
+                }
+            }
+        };
+        var mouseUp = function (e) {
+            setOrderField();
+            document.body.removeChild(dTable);
+            delEvent(document.body, "mouseup", mouseUp);
+            delEvent(document.body, "mousemove", mouseMove);
+        };
+        var rowPos = absNodePos(targetRow);
+        dTable = detachedRowClone(targetRow);
+        document.body.insertBefore(dTable, document.body.firstChild);
+        offsetX = mouseX - (rowPos.x - dTable.rows[0].offsetLeft);
+        offsetY = mouseY - (rowPos.y - dTable.rows[0].offsetTop);
+        moveDetached(mouseX, mouseY);
+        addEvent(document.body, "mouseup", mouseUp);
+        addEvent(document.body, "mousemove", mouseMove);
+    };
+    var mouseDown = function (e) {
+        var ev = e || event,
+            view = viewTopLeft(),
+            targetRow;
+        targetRow = parentTag(ev.target || ev.srcElement, 'tr');
+        if (targetRow) {
+            startMove(targetRow, view.left + ev.screenX, view.top + ev.screenY);
+        }
+        return cancelEvent(ev);
+    };
+    classApply(theTable, "move-handle", 
+        function (handleNode) {
+            var rowNode = parentTag(handleNode, 'tr');
+            if (rowNode) {
+                if (!rowNode.id) {
+                    rowNode.id = tableName + ':' + nMoveableRows;
+                }
+                idMap[rowNode.id] = nMoveableRows++;
+                handleNode.style.cursor = 'pointer';
+                addEvent(handleNode, 'mousedown', mouseDown);
+            }
+        }
+    );
+};
diff --git a/app/app.py b/app/app.py
new file mode 100644
index 0000000..7124fa7
--- /dev/null
+++ b/app/app.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import time
+
+# Get globals setup out of the way early as other modules depend on it.
+from casemgr import globals
+from cocklebur import dbobj
+from casemgr import albasetup, handle_exception, persondupestat
+from casemgr.notification.client import connect as notify_connect
+import config
+
+config_vars = (
+    'appname',
+    'apptitle',
+    'debug',
+    'helpdesk_contact',
+    'html_target',
+    'session_timeout',
+)
+
+dbobj.execute_debug(config.tracedb)
+dbobj.execute_timing(config.exec_timing)
+
+globals.notify = notify_connect(config.cgi_target,
+                                config.notification_host,
+                                config.notification_port)
+
+persondupestat.dupescan_subscribe()
+
+
+app = albasetup.get_app(config, config_vars, 
+                        base_url='app.py', 
+                        module_path='pages', 
+                        template_path = 'pages', 
+                        start_page = 'login')
+
+
+if __name__ == '__main__':
+
+    req_count = 0
+    for req in albasetup.next_request():
+        if req.get_method() == 'OPTIONS':
+            # MS Sharepoint emits HTTP 1.1 OPTIONS methods to determine if the
+            # server supports WebDAV - the cgi module generates a broken
+            # FieldStorage object in this case, so we short-circuit the
+            # request.
+            req.end_headers()
+            req.return_code()
+            continue
+        req_count += 1
+        globals.remote_host = req.get_remote_host()
+        try:
+            globals.db.load_describer()         # Pick up any schema changes
+            app.run(req)
+            globals.notify.poll()
+        finally:
+            globals.db.rollback()
+        if config.exec_timing:
+            from cocklebur.dbobj import exec_timing
+            exec_timing.show()
+        if config.max_requests and req_count >= config.max_requests:
+            # After servicing this many requests, we exit gracefully to
+            # minimise the impact of memory fragmentation and object leaks.
+            break
diff --git a/app/backiframe.html b/app/backiframe.html
new file mode 100644
index 0000000..8e0ee0c
--- /dev/null
+++ b/app/backiframe.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+    "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+  <title>NetEpi Back Button Helper</title>
+ </head>
+ <body onload="parent._nbb_frame_load(window);"></body>
+</html>
diff --git a/app/calendar.css b/app/calendar.css
new file mode 100644
index 0000000..c42da5e
--- /dev/null
+++ b/app/calendar.css
@@ -0,0 +1,225 @@
+/* The main calendar widget.  DIV containing a table. */
+
+div.calendar { position: relative; }
+
+.calendar, .calendar table {
+  border: 1px solid #655;
+  font-size: 11px;
+  color: #000;
+  cursor: default;
+  background: #ffd;
+  font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+  text-align: center;    /* They are the navigation buttons */
+  padding: 2px;          /* Make the buttons seem like they're pressing */
+}
+
+.calendar .nav {
+  background: #edc url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+  font-weight: bold;      /* Pressing it will take you to the current date */
+  text-align: center;
+  background: #654;
+  color: #fed;
+  padding: 2px;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+  background: #edc;
+  color: #000;
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+  border-bottom: 1px solid #655;
+  padding: 2px;
+  text-align: center;
+  color: #000;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+  color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+  background-color: #faa;
+  color: #000;
+  border: 1px solid #f40;
+  padding: 1px;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+  background-color: #c77;
+  padding: 2px 0px 0px 2px;
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+  background: #fed;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+  width: 2em;
+  text-align: right;
+  padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+  font-size: 80%;
+  color: #bbb;
+}
+.calendar tbody .day.othermonth.oweekend {
+  color: #fbb;
+}
+
+.calendar table .wn {
+  padding: 2px 3px 2px 2px;
+  border-right: 1px solid #000;
+  background: #fed;
+}
+
+.calendar tbody .rowhilite td {
+  background: #ddf;
+}
+
+.calendar tbody .rowhilite td.wn {
+  background: #efe;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+  background: #ffe;
+  padding: 1px 3px 1px 1px;
+  border: 1px solid #bbb;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+  background: #ddc;
+  padding: 2px 2px 0px 2px;
+}
+
+.calendar tbody td.selected { /* Cell showing today date */
+  font-weight: bold;
+  border: 1px solid #000;
+  padding: 1px 3px 1px 1px;
+  background: #fea;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+  color: #f00;
+}
+
+.calendar tbody td.today { font-weight: bold; }
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+  visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+  display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+  text-align: center;
+  background: #988;
+  color: #000;
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+  border-top: 1px solid #655;
+  background: #dcb;
+  color: #840;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+  background: #faa;
+  border: 1px solid #f40;
+  padding: 1px;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+  background: #c77;
+  padding: 2px 0px 0px 2px;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+  position: absolute;
+  display: none;
+  top: 0px;
+  left: 0px;
+  width: 4em;
+  cursor: default;
+  border: 1px solid #655;
+  background: #ffe;
+  color: #000;
+  font-size: 90%;
+  z-index: 100;
+}
+
+.calendar .combo .label,
+.calendar .combo .label-IEfix {
+  text-align: center;
+  padding: 1px;
+}
+
+.calendar .combo .label-IEfix {
+  width: 4em;
+}
+
+.calendar .combo .hilite {
+  background: #fc8;
+}
+
+.calendar .combo .active {
+  border-top: 1px solid #a64;
+  border-bottom: 1px solid #a64;
+  background: #fee;
+  font-weight: bold;
+}
+
+.calendar td.time {
+  border-top: 1px solid #a88;
+  padding: 1px 0px;
+  text-align: center;
+  background-color: #fed;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+  padding: 0px 3px 0px 4px;
+  border: 1px solid #988;
+  font-weight: bold;
+  background-color: #fff;
+}
+
+.calendar td.time .ampm {
+  text-align: center;
+}
+
+.calendar td.time .colon {
+  padding: 0px 2px 0px 3px;
+  font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+  border-color: #000;
+  background-color: #866;
+  color: #fff;
+}
+
+.calendar td.time span.active {
+  border-color: #f00;
+  background-color: #000;
+  color: #0f0;
+}
diff --git a/app/calendar.js b/app/calendar.js
new file mode 100644
index 0000000..2faafa7
--- /dev/null
+++ b/app/calendar.js
@@ -0,0 +1,1806 @@
+/*  Copyright Mihai Bazon, 2002-2005  |  www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com.  Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+	// member variables
+	this.activeDiv = null;
+	this.currentDateEl = null;
+	this.getDateStatus = null;
+	this.getDateToolTip = null;
+	this.getDateText = null;
+	this.timeout = null;
+	this.onSelected = onSelected || null;
+	this.onClose = onClose || null;
+	this.dragging = false;
+	this.hidden = false;
+	this.minYear = 1970;
+	this.maxYear = 2050;
+	this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+	this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+	this.isPopup = true;
+	this.weekNumbers = true;
+	this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc.
+	this.showsOtherMonths = false;
+	this.dateStr = dateStr;
+	this.ar_days = null;
+	this.showsTime = false;
+	this.time24 = true;
+	this.yearStep = 2;
+	this.hiliteToday = true;
+	this.multiple = null;
+	// HTML elements
+	this.table = null;
+	this.element = null;
+	this.tbody = null;
+	this.firstdayname = null;
+	// Combo boxes
+	this.monthsCombo = null;
+	this.yearsCombo = null;
+	this.hilitedMonth = null;
+	this.activeMonth = null;
+	this.hilitedYear = null;
+	this.activeYear = null;
+	// Information
+	this.dateClicked = false;
+
+	// one-time initializations
+	if (typeof Calendar._SDN == "undefined") {
+		// table of short day names
+		if (typeof Calendar._SDN_len == "undefined")
+			Calendar._SDN_len = 3;
+		var ar = new Array();
+		for (var i = 8; i > 0;) {
+			ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+		}
+		Calendar._SDN = ar;
+		// table of short month names
+		if (typeof Calendar._SMN_len == "undefined")
+			Calendar._SMN_len = 3;
+		ar = new Array();
+		for (var i = 12; i > 0;) {
+			ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+		}
+		Calendar._SMN = ar;
+	}
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+		   !/opera/i.test(navigator.userAgent) );
+
+Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+//        library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+	var SL = 0, ST = 0;
+	var is_div = /^div$/i.test(el.tagName);
+	if (is_div && el.scrollLeft)
+		SL = el.scrollLeft;
+	if (is_div && el.scrollTop)
+		ST = el.scrollTop;
+	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+	if (el.offsetParent) {
+		var tmp = this.getAbsolutePos(el.offsetParent);
+		r.x += tmp.x;
+		r.y += tmp.y;
+	}
+	return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+	var related = evt.relatedTarget;
+	if (!related) {
+		var type = evt.type;
+		if (type == "mouseover") {
+			related = evt.fromElement;
+		} else if (type == "mouseout") {
+			related = evt.toElement;
+		}
+	}
+	while (related) {
+		if (related == el) {
+			return true;
+		}
+		related = related.parentNode;
+	}
+	return false;
+};
+
+Calendar.removeClass = function(el, className) {
+	if (!(el && el.className)) {
+		return;
+	}
+	var cls = el.className.split(" ");
+	var ar = new Array();
+	for (var i = cls.length; i > 0;) {
+		if (cls[--i] != className) {
+			ar[ar.length] = cls[i];
+		}
+	}
+	el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+	Calendar.removeClass(el, className);
+	el.className += " " + className;
+};
+
+// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
+Calendar.getElement = function(ev) {
+	var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
+	while (f.nodeType != 1 || /^div$/i.test(f.tagName))
+		f = f.parentNode;
+	return f;
+};
+
+Calendar.getTargetElement = function(ev) {
+	var f = Calendar.is_ie ? window.event.srcElement : ev.target;
+	while (f.nodeType != 1)
+		f = f.parentNode;
+	return f;
+};
+
+Calendar.stopEvent = function(ev) {
+	ev || (ev = window.event);
+	if (Calendar.is_ie) {
+		ev.cancelBubble = true;
+		ev.returnValue = false;
+	} else {
+		ev.preventDefault();
+		ev.stopPropagation();
+	}
+	return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+	if (el.attachEvent) { // IE
+		el.attachEvent("on" + evname, func);
+	} else if (el.addEventListener) { // Gecko / W3C
+		el.addEventListener(evname, func, true);
+	} else {
+		el["on" + evname] = func;
+	}
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+	if (el.detachEvent) { // IE
+		el.detachEvent("on" + evname, func);
+	} else if (el.removeEventListener) { // Gecko / W3C
+		el.removeEventListener(evname, func, true);
+	} else {
+		el["on" + evname] = null;
+	}
+};
+
+Calendar.createElement = function(type, parent) {
+	var el = null;
+	if (document.createElementNS) {
+		// use the XHTML namespace; IE won't normally get here unless
+		// _they_ "fix" the DOM2 implementation.
+		el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+	} else {
+		el = document.createElement(type);
+	}
+	if (typeof parent != "undefined") {
+		parent.appendChild(el);
+	}
+	return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+	with (Calendar) {
+		addEvent(el, "mouseover", dayMouseOver);
+		addEvent(el, "mousedown", dayMouseDown);
+		addEvent(el, "mouseout", dayMouseOut);
+		if (is_ie) {
+			addEvent(el, "dblclick", dayMouseDblClick);
+			el.setAttribute("unselectable", true);
+		}
+	}
+};
+
+Calendar.findMonth = function(el) {
+	if (typeof el.month != "undefined") {
+		return el;
+	} else if (typeof el.parentNode.month != "undefined") {
+		return el.parentNode;
+	}
+	return null;
+};
+
+Calendar.findYear = function(el) {
+	if (typeof el.year != "undefined") {
+		return el;
+	} else if (typeof el.parentNode.year != "undefined") {
+		return el.parentNode;
+	}
+	return null;
+};
+
+Calendar.showMonthsCombo = function () {
+	var cal = Calendar._C;
+	if (!cal) {
+		return false;
+	}
+	var cal = cal;
+	var cd = cal.activeDiv;
+	var mc = cal.monthsCombo;
+	if (cal.hilitedMonth) {
+		Calendar.removeClass(cal.hilitedMonth, "hilite");
+	}
+	if (cal.activeMonth) {
+		Calendar.removeClass(cal.activeMonth, "active");
+	}
+	var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+	Calendar.addClass(mon, "active");
+	cal.activeMonth = mon;
+	var s = mc.style;
+	s.display = "block";
+	if (cd.navtype < 0)
+		s.left = cd.offsetLeft + "px";
+	else {
+		var mcw = mc.offsetWidth;
+		if (typeof mcw == "undefined")
+			// Konqueror brain-dead techniques
+			mcw = 50;
+		s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
+	}
+	s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+	var cal = Calendar._C;
+	if (!cal) {
+		return false;
+	}
+	var cal = cal;
+	var cd = cal.activeDiv;
+	var yc = cal.yearsCombo;
+	if (cal.hilitedYear) {
+		Calendar.removeClass(cal.hilitedYear, "hilite");
+	}
+	if (cal.activeYear) {
+		Calendar.removeClass(cal.activeYear, "active");
+	}
+	cal.activeYear = null;
+	var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+	var yr = yc.firstChild;
+	var show = false;
+	for (var i = 12; i > 0; --i) {
+		if (Y >= cal.minYear && Y <= cal.maxYear) {
+			yr.innerHTML = Y;
+			yr.year = Y;
+			yr.style.display = "block";
+			show = true;
+		} else {
+			yr.style.display = "none";
+		}
+		yr = yr.nextSibling;
+		Y += fwd ? cal.yearStep : -cal.yearStep;
+	}
+	if (show) {
+		var s = yc.style;
+		s.display = "block";
+		if (cd.navtype < 0)
+			s.left = cd.offsetLeft + "px";
+		else {
+			var ycw = yc.offsetWidth;
+			if (typeof ycw == "undefined")
+				// Konqueror brain-dead techniques
+				ycw = 50;
+			s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
+		}
+		s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+	}
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+	var cal = Calendar._C;
+	if (!cal) {
+		return false;
+	}
+	if (cal.timeout) {
+		clearTimeout(cal.timeout);
+	}
+	var el = cal.activeDiv;
+	if (!el) {
+		return false;
+	}
+	var target = Calendar.getTargetElement(ev);
+	ev || (ev = window.event);
+	Calendar.removeClass(el, "active");
+	if (target == el || target.parentNode == el) {
+		Calendar.cellClick(el, ev);
+	}
+	var mon = Calendar.findMonth(target);
+	var date = null;
+	if (mon) {
+		date = new Date(cal.date);
+		if (mon.month != date.getMonth()) {
+			date.setMonth(mon.month);
+			cal.setDate(date);
+			cal.dateClicked = false;
+			cal.callHandler();
+		}
+	} else {
+		var year = Calendar.findYear(target);
+		if (year) {
+			date = new Date(cal.date);
+			if (year.year != date.getFullYear()) {
+				date.setFullYear(year.year);
+				cal.setDate(date);
+				cal.dateClicked = false;
+				cal.callHandler();
+			}
+		}
+	}
+	with (Calendar) {
+		removeEvent(document, "mouseup", tableMouseUp);
+		removeEvent(document, "mouseover", tableMouseOver);
+		removeEvent(document, "mousemove", tableMouseOver);
+		cal._hideCombos();
+		_C = null;
+		return stopEvent(ev);
+	}
+};
+
+Calendar.tableMouseOver = function (ev) {
+	var cal = Calendar._C;
+	if (!cal) {
+		return;
+	}
+	var el = cal.activeDiv;
+	var target = Calendar.getTargetElement(ev);
+	if (target == el || target.parentNode == el) {
+		Calendar.addClass(el, "hilite active");
+		Calendar.addClass(el.parentNode, "rowhilite");
+	} else {
+		if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+			Calendar.removeClass(el, "active");
+		Calendar.removeClass(el, "hilite");
+		Calendar.removeClass(el.parentNode, "rowhilite");
+	}
+	ev || (ev = window.event);
+	if (el.navtype == 50 && target != el) {
+		var pos = Calendar.getAbsolutePos(el);
+		var w = el.offsetWidth;
+		var x = ev.clientX;
+		var dx;
+		var decrease = true;
+		if (x > pos.x + w) {
+			dx = x - pos.x - w;
+			decrease = false;
+		} else
+			dx = pos.x - x;
+
+		if (dx < 0) dx = 0;
+		var range = el._range;
+		var current = el._current;
+		var count = Math.floor(dx / 10) % range.length;
+		for (var i = range.length; --i >= 0;)
+			if (range[i] == current)
+				break;
+		while (count-- > 0)
+			if (decrease) {
+				if (--i < 0)
+					i = range.length - 1;
+			} else if ( ++i >= range.length )
+				i = 0;
+		var newval = range[i];
+		el.innerHTML = newval;
+
+		cal.onUpdateTime();
+	}
+	var mon = Calendar.findMonth(target);
+	if (mon) {
+		if (mon.month != cal.date.getMonth()) {
+			if (cal.hilitedMonth) {
+				Calendar.removeClass(cal.hilitedMonth, "hilite");
+			}
+			Calendar.addClass(mon, "hilite");
+			cal.hilitedMonth = mon;
+		} else if (cal.hilitedMonth) {
+			Calendar.removeClass(cal.hilitedMonth, "hilite");
+		}
+	} else {
+		if (cal.hilitedMonth) {
+			Calendar.removeClass(cal.hilitedMonth, "hilite");
+		}
+		var year = Calendar.findYear(target);
+		if (year) {
+			if (year.year != cal.date.getFullYear()) {
+				if (cal.hilitedYear) {
+					Calendar.removeClass(cal.hilitedYear, "hilite");
+				}
+				Calendar.addClass(year, "hilite");
+				cal.hilitedYear = year;
+			} else if (cal.hilitedYear) {
+				Calendar.removeClass(cal.hilitedYear, "hilite");
+			}
+		} else if (cal.hilitedYear) {
+			Calendar.removeClass(cal.hilitedYear, "hilite");
+		}
+	}
+	return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+	if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+		return Calendar.stopEvent(ev);
+	}
+};
+
+Calendar.calDragIt = function (ev) {
+	var cal = Calendar._C;
+	if (!(cal && cal.dragging)) {
+		return false;
+	}
+	var posX;
+	var posY;
+	if (Calendar.is_ie) {
+		posY = window.event.clientY + document.body.scrollTop;
+		posX = window.event.clientX + document.body.scrollLeft;
+	} else {
+		posX = ev.pageX;
+		posY = ev.pageY;
+	}
+	cal.hideShowCovered();
+	var st = cal.element.style;
+	st.left = (posX - cal.xOffs) + "px";
+	st.top = (posY - cal.yOffs) + "px";
+	return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+	var cal = Calendar._C;
+	if (!cal) {
+		return false;
+	}
+	cal.dragging = false;
+	with (Calendar) {
+		removeEvent(document, "mousemove", calDragIt);
+		removeEvent(document, "mouseup", calDragEnd);
+		tableMouseUp(ev);
+	}
+	cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+	var el = Calendar.getElement(ev);
+	if (el.disabled) {
+		return false;
+	}
+	var cal = el.calendar;
+	cal.activeDiv = el;
+	Calendar._C = cal;
+	if (el.navtype != 300) with (Calendar) {
+		if (el.navtype == 50) {
+			el._current = el.innerHTML;
+			addEvent(document, "mousemove", tableMouseOver);
+		} else
+			addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
+		addClass(el, "hilite active");
+		addEvent(document, "mouseup", tableMouseUp);
+	} else if (cal.isPopup) {
+		cal._dragStart(ev);
+	}
+	if (el.navtype == -1 || el.navtype == 1) {
+		if (cal.timeout) clearTimeout(cal.timeout);
+		cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+	} else if (el.navtype == -2 || el.navtype == 2) {
+		if (cal.timeout) clearTimeout(cal.timeout);
+		cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+	} else {
+		cal.timeout = null;
+	}
+	return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+	Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+	if (Calendar.is_ie) {
+		document.selection.empty();
+	}
+};
+
+Calendar.dayMouseOver = function(ev) {
+	var el = Calendar.getElement(ev);
+	if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+		return false;
+	}
+	if (el.ttip) {
+		if (el.ttip.substr(0, 1) == "_") {
+			el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+		}
+		el.calendar.tooltips.innerHTML = el.ttip;
+	}
+	if (el.navtype != 300) {
+		Calendar.addClass(el, "hilite");
+		if (el.caldate) {
+			Calendar.addClass(el.parentNode, "rowhilite");
+		}
+	}
+	return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+	with (Calendar) {
+		var el = getElement(ev);
+		if (isRelated(el, ev) || _C || el.disabled)
+			return false;
+		removeClass(el, "hilite");
+		if (el.caldate)
+			removeClass(el.parentNode, "rowhilite");
+		if (el.calendar)
+			el.calendar.tooltips.innerHTML = _TT["SEL_DATE"];
+		return stopEvent(ev);
+	}
+};
+
+/**
+ *  A generic "click" handler :) handles all types of buttons defined in this
+ *  calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+	var cal = el.calendar;
+	var closing = false;
+	var newdate = false;
+	var date = null;
+	if (typeof el.navtype == "undefined") {
+		if (cal.currentDateEl) {
+			Calendar.removeClass(cal.currentDateEl, "selected");
+			Calendar.addClass(el, "selected");
+			closing = (cal.currentDateEl == el);
+			if (!closing) {
+				cal.currentDateEl = el;
+			}
+		}
+		cal.date.setDateOnly(el.caldate);
+		date = cal.date;
+		var other_month = !(cal.dateClicked = !el.otherMonth);
+		if (!other_month && !cal.currentDateEl)
+			cal._toggleMultipleDate(new Date(date));
+		else
+			newdate = !el.disabled;
+		// a date was clicked
+		if (other_month)
+			cal._init(cal.firstDayOfWeek, date);
+	} else {
+		if (el.navtype == 200) {
+			Calendar.removeClass(el, "hilite");
+			cal.callCloseHandler();
+			return;
+		}
+		date = new Date(cal.date);
+		if (el.navtype == 0)
+			date.setDateOnly(new Date()); // TODAY
+		// unless "today" was clicked, we assume no date was clicked so
+		// the selected handler will know not to close the calenar when
+		// in single-click mode.
+		// cal.dateClicked = (el.navtype == 0);
+		cal.dateClicked = false;
+		var year = date.getFullYear();
+		var mon = date.getMonth();
+		function setMonth(m) {
+			var day = date.getDate();
+			var max = date.getMonthDays(m);
+			if (day > max) {
+				date.setDate(max);
+			}
+			date.setMonth(m);
+		};
+		switch (el.navtype) {
+		    case 400:
+			Calendar.removeClass(el, "hilite");
+			var text = Calendar._TT["ABOUT"];
+			if (typeof text != "undefined") {
+				text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+			} else {
+				// FIXME: this should be removed as soon as lang files get updated!
+				text = "Help and about box text is not translated into this language.\n" +
+					"If you know this language and you feel generous please update\n" +
+					"the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+					"and send it back to <mihai_bazon at yahoo.com> to get it into the distribution  ;-)\n\n" +
+					"Thank you!\n" +
+					"http://dynarch.com/mishoo/calendar.epl\n";
+			}
+			alert(text);
+			return;
+		    case -2:
+			if (year > cal.minYear) {
+				date.setFullYear(year - 1);
+			}
+			break;
+		    case -1:
+			if (mon > 0) {
+				setMonth(mon - 1);
+			} else if (year-- > cal.minYear) {
+				date.setFullYear(year);
+				setMonth(11);
+			}
+			break;
+		    case 1:
+			if (mon < 11) {
+				setMonth(mon + 1);
+			} else if (year < cal.maxYear) {
+				date.setFullYear(year + 1);
+				setMonth(0);
+			}
+			break;
+		    case 2:
+			if (year < cal.maxYear) {
+				date.setFullYear(year + 1);
+			}
+			break;
+		    case 100:
+			cal.setFirstDayOfWeek(el.fdow);
+			return;
+		    case 50:
+			var range = el._range;
+			var current = el.innerHTML;
+			for (var i = range.length; --i >= 0;)
+				if (range[i] == current)
+					break;
+			if (ev && ev.shiftKey) {
+				if (--i < 0)
+					i = range.length - 1;
+			} else if ( ++i >= range.length )
+				i = 0;
+			var newval = range[i];
+			el.innerHTML = newval;
+			cal.onUpdateTime();
+			return;
+		    case 0:
+			// TODAY will bring us here
+			if ((typeof cal.getDateStatus == "function") &&
+			    cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+				return false;
+			}
+			break;
+		}
+		if (!date.equalsTo(cal.date)) {
+			cal.setDate(date);
+			newdate = true;
+		} else if (el.navtype == 0)
+			newdate = closing = true;
+	}
+	if (newdate) {
+		ev && cal.callHandler();
+	}
+	if (closing) {
+		Calendar.removeClass(el, "hilite");
+		ev && cal.callCloseHandler();
+	}
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ *  This function creates the calendar inside the given parent.  If _par is
+ *  null than it creates a popup calendar inside the BODY element.  If _par is
+ *  an element, be it BODY, then it creates a non-popup calendar (still
+ *  hidden).  Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+	var parent = null;
+	if (! _par) {
+		// default parent is the document body, in which case we create
+		// a popup calendar.
+		parent = document.getElementsByTagName("body")[0];
+		this.isPopup = true;
+	} else {
+		parent = _par;
+		this.isPopup = false;
+	}
+	this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+	var table = Calendar.createElement("table");
+	this.table = table;
+	table.cellSpacing = 0;
+	table.cellPadding = 0;
+	table.calendar = this;
+	Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+	var div = Calendar.createElement("div");
+	this.element = div;
+	div.className = "calendar";
+	if (this.isPopup) {
+		div.style.position = "absolute";
+		div.style.display = "none";
+	}
+	div.appendChild(table);
+
+	var thead = Calendar.createElement("thead", table);
+	var cell = null;
+	var row = null;
+
+	var cal = this;
+	var hh = function (text, cs, navtype) {
+		cell = Calendar.createElement("td", row);
+		cell.colSpan = cs;
+		cell.className = "button";
+		if (navtype != 0 && Math.abs(navtype) <= 2)
+			cell.className += " nav";
+		Calendar._add_evs(cell);
+		cell.calendar = cal;
+		cell.navtype = navtype;
+		cell.innerHTML = "<div unselectable='on'>" + text + "</div>";
+		return cell;
+	};
+
+	row = Calendar.createElement("tr", thead);
+	var title_length = 6;
+	(this.isPopup) && --title_length;
+	(this.weekNumbers) && ++title_length;
+
+	hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+	this.title = hh("", title_length, 300);
+	this.title.className = "title";
+	if (this.isPopup) {
+		this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+		this.title.style.cursor = "move";
+		hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
+	}
+
+	row = Calendar.createElement("tr", thead);
+	row.className = "headrow";
+
+	this._nav_py = hh("&#x00ab;", 1, -2);
+	this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+	this._nav_pm = hh("&#x2039;", 1, -1);
+	this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+	this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+	this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+	this._nav_nm = hh("&#x203a;", 1, 1);
+	this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+	this._nav_ny = hh("&#x00bb;", 1, 2);
+	this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+	// day names
+	row = Calendar.createElement("tr", thead);
+	row.className = "daynames";
+	if (this.weekNumbers) {
+		cell = Calendar.createElement("td", row);
+		cell.className = "name wn";
+		cell.innerHTML = Calendar._TT["WK"];
+	}
+	for (var i = 7; i > 0; --i) {
+		cell = Calendar.createElement("td", row);
+		if (!i) {
+			cell.navtype = 100;
+			cell.calendar = this;
+			Calendar._add_evs(cell);
+		}
+	}
+	this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+	this._displayWeekdays();
+
+	var tbody = Calendar.createElement("tbody", table);
+	this.tbody = tbody;
+
+	for (i = 6; i > 0; --i) {
+		row = Calendar.createElement("tr", tbody);
+		if (this.weekNumbers) {
+			cell = Calendar.createElement("td", row);
+		}
+		for (var j = 7; j > 0; --j) {
+			cell = Calendar.createElement("td", row);
+			cell.calendar = this;
+			Calendar._add_evs(cell);
+		}
+	}
+
+	if (this.showsTime) {
+		row = Calendar.createElement("tr", tbody);
+		row.className = "time";
+
+		cell = Calendar.createElement("td", row);
+		cell.className = "time";
+		cell.colSpan = 2;
+		cell.innerHTML = Calendar._TT["TIME"] || " ";
+
+		cell = Calendar.createElement("td", row);
+		cell.className = "time";
+		cell.colSpan = this.weekNumbers ? 4 : 3;
+
+		(function(){
+			function makeTimePart(className, init, range_start, range_end) {
+				var part = Calendar.createElement("span", cell);
+				part.className = className;
+				part.innerHTML = init;
+				part.calendar = cal;
+				part.ttip = Calendar._TT["TIME_PART"];
+				part.navtype = 50;
+				part._range = [];
+				if (typeof range_start != "number")
+					part._range = range_start;
+				else {
+					for (var i = range_start; i <= range_end; ++i) {
+						var txt;
+						if (i < 10 && range_end >= 10) txt = '0' + i;
+						else txt = '' + i;
+						part._range[part._range.length] = txt;
+					}
+				}
+				Calendar._add_evs(part);
+				return part;
+			};
+			var hrs = cal.date.getHours();
+			var mins = cal.date.getMinutes();
+			var t12 = !cal.time24;
+			var pm = (hrs > 12);
+			if (t12 && pm) hrs -= 12;
+			var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+			var span = Calendar.createElement("span", cell);
+			span.innerHTML = ":";
+			span.className = "colon";
+			var M = makeTimePart("minute", mins, 0, 59);
+			var AP = null;
+			cell = Calendar.createElement("td", row);
+			cell.className = "time";
+			cell.colSpan = 2;
+			if (t12)
+				AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+			else
+				cell.innerHTML = " ";
+
+			cal.onSetTime = function() {
+				var pm, hrs = this.date.getHours(),
+					mins = this.date.getMinutes();
+				if (t12) {
+					pm = (hrs >= 12);
+					if (pm) hrs -= 12;
+					if (hrs == 0) hrs = 12;
+					AP.innerHTML = pm ? "pm" : "am";
+				}
+				H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
+				M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
+			};
+
+			cal.onUpdateTime = function() {
+				var date = this.date;
+				var h = parseInt(H.innerHTML, 10);
+				if (t12) {
+					if (/pm/i.test(AP.innerHTML) && h < 12)
+						h += 12;
+					else if (/am/i.test(AP.innerHTML) && h == 12)
+						h = 0;
+				}
+				var d = date.getDate();
+				var m = date.getMonth();
+				var y = date.getFullYear();
+				date.setHours(h);
+				date.setMinutes(parseInt(M.innerHTML, 10));
+				date.setFullYear(y);
+				date.setMonth(m);
+				date.setDate(d);
+				this.dateClicked = false;
+				this.callHandler();
+			};
+		})();
+	} else {
+		this.onSetTime = this.onUpdateTime = function() {};
+	}
+
+	var tfoot = Calendar.createElement("tfoot", table);
+
+	row = Calendar.createElement("tr", tfoot);
+	row.className = "footrow";
+
+	cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+	cell.className = "ttip";
+	if (this.isPopup) {
+		cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+		cell.style.cursor = "move";
+	}
+	this.tooltips = cell;
+
+	div = Calendar.createElement("div", this.element);
+	this.monthsCombo = div;
+	div.className = "combo";
+	for (i = 0; i < Calendar._MN.length; ++i) {
+		var mn = Calendar.createElement("div");
+		mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+		mn.month = i;
+		mn.innerHTML = Calendar._SMN[i];
+		div.appendChild(mn);
+	}
+
+	div = Calendar.createElement("div", this.element);
+	this.yearsCombo = div;
+	div.className = "combo";
+	for (i = 12; i > 0; --i) {
+		var yr = Calendar.createElement("div");
+		yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+		div.appendChild(yr);
+	}
+
+	this._init(this.firstDayOfWeek, this.date);
+	parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+	var cal = window._dynarch_popupCalendar;
+	if (!cal || cal.multiple)
+		return false;
+	(Calendar.is_ie) && (ev = window.event);
+	var act = (Calendar.is_ie || ev.type == "keypress"),
+		K = ev.keyCode;
+	if (ev.ctrlKey) {
+		switch (K) {
+		    case 37: // KEY left
+			act && Calendar.cellClick(cal._nav_pm);
+			break;
+		    case 38: // KEY up
+			act && Calendar.cellClick(cal._nav_py);
+			break;
+		    case 39: // KEY right
+			act && Calendar.cellClick(cal._nav_nm);
+			break;
+		    case 40: // KEY down
+			act && Calendar.cellClick(cal._nav_ny);
+			break;
+		    default:
+			return false;
+		}
+	} else switch (K) {
+	    case 32: // KEY space (now)
+		Calendar.cellClick(cal._nav_now);
+		break;
+	    case 27: // KEY esc
+		act && cal.callCloseHandler();
+		break;
+	    case 37: // KEY left
+	    case 38: // KEY up
+	    case 39: // KEY right
+	    case 40: // KEY down
+		if (act) {
+			var prev, x, y, ne, el, step;
+			prev = K == 37 || K == 38;
+			step = (K == 37 || K == 39) ? 1 : 7;
+			function setVars() {
+				el = cal.currentDateEl;
+				var p = el.pos;
+				x = p & 15;
+				y = p >> 4;
+				ne = cal.ar_days[y][x];
+			};setVars();
+			function prevMonth() {
+				var date = new Date(cal.date);
+				date.setDate(date.getDate() - step);
+				cal.setDate(date);
+			};
+			function nextMonth() {
+				var date = new Date(cal.date);
+				date.setDate(date.getDate() + step);
+				cal.setDate(date);
+			};
+			while (1) {
+				switch (K) {
+				    case 37: // KEY left
+					if (--x >= 0)
+						ne = cal.ar_days[y][x];
+					else {
+						x = 6;
+						K = 38;
+						continue;
+					}
+					break;
+				    case 38: // KEY up
+					if (--y >= 0)
+						ne = cal.ar_days[y][x];
+					else {
+						prevMonth();
+						setVars();
+					}
+					break;
+				    case 39: // KEY right
+					if (++x < 7)
+						ne = cal.ar_days[y][x];
+					else {
+						x = 0;
+						K = 40;
+						continue;
+					}
+					break;
+				    case 40: // KEY down
+					if (++y < cal.ar_days.length)
+						ne = cal.ar_days[y][x];
+					else {
+						nextMonth();
+						setVars();
+					}
+					break;
+				}
+				break;
+			}
+			if (ne) {
+				if (!ne.disabled)
+					Calendar.cellClick(ne);
+				else if (prev)
+					prevMonth();
+				else
+					nextMonth();
+			}
+		}
+		break;
+	    case 13: // KEY enter
+		if (act)
+			Calendar.cellClick(cal.currentDateEl, ev);
+		break;
+	    default:
+		return false;
+	}
+	return Calendar.stopEvent(ev);
+};
+
+/**
+ *  (RE)Initializes the calendar to the given date and firstDayOfWeek
+ */
+Calendar.prototype._init = function (firstDayOfWeek, date) {
+	var today = new Date(),
+		TY = today.getFullYear(),
+		TM = today.getMonth(),
+		TD = today.getDate();
+	this.table.style.visibility = "hidden";
+	var year = date.getFullYear();
+	if (year < this.minYear) {
+		year = this.minYear;
+		date.setFullYear(year);
+	} else if (year > this.maxYear) {
+		year = this.maxYear;
+		date.setFullYear(year);
+	}
+	this.firstDayOfWeek = firstDayOfWeek;
+	this.date = new Date(date);
+	var month = date.getMonth();
+	var mday = date.getDate();
+	var no_days = date.getMonthDays();
+
+	// calendar voodoo for computing the first day that would actually be
+	// displayed in the calendar, even if it's from the previous month.
+	// WARNING: this is magic. ;-)
+	date.setDate(1);
+	var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
+	if (day1 < 0)
+		day1 += 7;
+	date.setDate(-day1);
+	date.setDate(date.getDate() + 1);
+
+	var row = this.tbody.firstChild;
+	var MN = Calendar._SMN[month];
+	var ar_days = this.ar_days = new Array();
+	var weekend = Calendar._TT["WEEKEND"];
+	var dates = this.multiple ? (this.datesCells = {}) : null;
+	for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+		var cell = row.firstChild;
+		if (this.weekNumbers) {
+			cell.className = "day wn";
+			cell.innerHTML = date.getWeekNumber();
+			cell = cell.nextSibling;
+		}
+		row.className = "daysrow";
+		var hasdays = false, iday, dpos = ar_days[i] = [];
+		for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
+			iday = date.getDate();
+			var wday = date.getDay();
+			cell.className = "day";
+			cell.pos = i << 4 | j;
+			dpos[j] = cell;
+			var current_month = (date.getMonth() == month);
+			if (!current_month) {
+				if (this.showsOtherMonths) {
+					cell.className += " othermonth";
+					cell.otherMonth = true;
+				} else {
+					cell.className = "emptycell";
+					cell.innerHTML = " ";
+					cell.disabled = true;
+					continue;
+				}
+			} else {
+				cell.otherMonth = false;
+				hasdays = true;
+			}
+			cell.disabled = false;
+			cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
+			if (dates)
+				dates[date.print("%Y%m%d")] = cell;
+			if (this.getDateStatus) {
+				var status = this.getDateStatus(date, year, month, iday);
+				if (this.getDateToolTip) {
+					var toolTip = this.getDateToolTip(date, year, month, iday);
+					if (toolTip)
+						cell.title = toolTip;
+				}
+				if (status === true) {
+					cell.className += " disabled";
+					cell.disabled = true;
+				} else {
+					if (/disabled/i.test(status))
+						cell.disabled = true;
+					cell.className += " " + status;
+				}
+			}
+			if (!cell.disabled) {
+				cell.caldate = new Date(date);
+				cell.ttip = "_";
+				if (!this.multiple && current_month
+				    && iday == mday && this.hiliteToday) {
+					cell.className += " selected";
+					this.currentDateEl = cell;
+				}
+				if (date.getFullYear() == TY &&
+				    date.getMonth() == TM &&
+				    iday == TD) {
+					cell.className += " today";
+					cell.ttip += Calendar._TT["PART_TODAY"];
+				}
+				if (weekend.indexOf(wday.toString()) != -1)
+					cell.className += cell.otherMonth ? " oweekend" : " weekend";
+			}
+		}
+		if (!(hasdays || this.showsOtherMonths))
+			row.className = "emptyrow";
+	}
+	this.title.innerHTML = Calendar._MN[month] + ", " + year;
+	this.onSetTime();
+	this.table.style.visibility = "visible";
+	this._initMultipleDates();
+	// PROFILE
+	// this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+Calendar.prototype._initMultipleDates = function() {
+	if (this.multiple) {
+		for (var i in this.multiple) {
+			var cell = this.datesCells[i];
+			var d = this.multiple[i];
+			if (!d)
+				continue;
+			if (cell)
+				cell.className += " selected";
+		}
+	}
+};
+
+Calendar.prototype._toggleMultipleDate = function(date) {
+	if (this.multiple) {
+		var ds = date.print("%Y%m%d");
+		var cell = this.datesCells[ds];
+		if (cell) {
+			var d = this.multiple[ds];
+			if (!d) {
+				Calendar.addClass(cell, "selected");
+				this.multiple[ds] = date;
+			} else {
+				Calendar.removeClass(cell, "selected");
+				delete this.multiple[ds];
+			}
+		}
+	}
+};
+
+Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
+	this.getDateToolTip = unaryFunction;
+};
+
+/**
+ *  Calls _init function above for going to a certain date (but only if the
+ *  date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+	if (!date.equalsTo(this.date)) {
+		this._init(this.firstDayOfWeek, date);
+	}
+};
+
+/**
+ *  Refreshes the calendar.  Useful if the "disabledHandler" function is
+ *  dynamic, meaning that the list of disabled date can change at runtime.
+ *  Just * call this function if you think that the list of disabled dates
+ *  should * change.
+ */
+Calendar.prototype.refresh = function () {
+	this._init(this.firstDayOfWeek, this.date);
+};
+
+/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
+Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
+	this._init(firstDayOfWeek, this.date);
+	this._displayWeekdays();
+};
+
+/**
+ *  Allows customization of what dates are enabled.  The "unaryFunction"
+ *  parameter must be a function object that receives the date (as a JS Date
+ *  object) and returns a boolean value.  If the returned value is true then
+ *  the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+	this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+	this.minYear = a;
+	this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+	if (this.onSelected) {
+		this.onSelected(this, this.date.print(this.dateFormat));
+	}
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+	if (this.onClose) {
+		this.onClose(this);
+	}
+	this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+	var el = this.element.parentNode;
+	el.removeChild(this.element);
+	Calendar._C = null;
+	window._dynarch_popupCalendar = null;
+};
+
+/**
+ *  Moves the calendar element to a different section in the DOM tree (changes
+ *  its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+	var el = this.element;
+	el.parentNode.removeChild(el);
+	new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown.  If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+	var calendar = window._dynarch_popupCalendar;
+	if (!calendar) {
+		return false;
+	}
+	var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+	for (; el != null && el != calendar.element; el = el.parentNode);
+	if (el == null) {
+		// calls closeHandler which should hide the calendar.
+		window._dynarch_popupCalendar.callCloseHandler();
+		return Calendar.stopEvent(ev);
+	}
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+	var rows = this.table.getElementsByTagName("tr");
+	for (var i = rows.length; i > 0;) {
+		var row = rows[--i];
+		Calendar.removeClass(row, "rowhilite");
+		var cells = row.getElementsByTagName("td");
+		for (var j = cells.length; j > 0;) {
+			var cell = cells[--j];
+			Calendar.removeClass(cell, "hilite");
+			Calendar.removeClass(cell, "active");
+		}
+	}
+	this.element.style.display = "block";
+	this.hidden = false;
+	if (this.isPopup) {
+		window._dynarch_popupCalendar = this;
+		Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+		Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+		Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+	}
+	this.hideShowCovered();
+};
+
+/**
+ *  Hides the calendar.  Also removes any "hilite" from the class of any TD
+ *  element.
+ */
+Calendar.prototype.hide = function () {
+	if (this.isPopup) {
+		Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+		Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+		Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+	}
+	this.element.style.display = "none";
+	this.hidden = true;
+	this.hideShowCovered();
+};
+
+/**
+ *  Shows the calendar at a given absolute position (beware that, depending on
+ *  the calendar element style -- position property -- this might be relative
+ *  to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+	var s = this.element.style;
+	s.left = x + "px";
+	s.top = y + "px";
+	this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+	var self = this;
+	var p = Calendar.getAbsolutePos(el);
+	if (!opts || typeof opts != "string") {
+		this.showAt(p.x, p.y + el.offsetHeight);
+		return true;
+	}
+	function fixPosition(box) {
+		if (box.x < 0)
+			box.x = 0;
+		if (box.y < 0)
+			box.y = 0;
+		var cp = document.createElement("div");
+		var s = cp.style;
+		s.position = "absolute";
+		s.right = s.bottom = s.width = s.height = "0px";
+		document.body.appendChild(cp);
+		var br = Calendar.getAbsolutePos(cp);
+		document.body.removeChild(cp);
+		if (Calendar.is_ie) {
+			br.y += document.body.scrollTop;
+			br.x += document.body.scrollLeft;
+		} else {
+			br.y += window.scrollY;
+			br.x += window.scrollX;
+		}
+		var tmp = box.x + box.width - br.x;
+		if (tmp > 0) box.x -= tmp;
+		tmp = box.y + box.height - br.y;
+		if (tmp > 0) box.y -= tmp;
+	};
+	this.element.style.display = "block";
+	Calendar.continuation_for_the_khtml_browser = function() {
+		var w = self.element.offsetWidth;
+		var h = self.element.offsetHeight;
+		self.element.style.display = "none";
+		var valign = opts.substr(0, 1);
+		var halign = "l";
+		if (opts.length > 1) {
+			halign = opts.substr(1, 1);
+		}
+		// vertical alignment
+		switch (valign) {
+		    case "T": p.y -= h; break;
+		    case "B": p.y += el.offsetHeight; break;
+		    case "C": p.y += (el.offsetHeight - h) / 2; break;
+		    case "t": p.y += el.offsetHeight - h; break;
+		    case "b": break; // already there
+		}
+		// horizontal alignment
+		switch (halign) {
+		    case "L": p.x -= w; break;
+		    case "R": p.x += el.offsetWidth; break;
+		    case "C": p.x += (el.offsetWidth - w) / 2; break;
+		    case "l": p.x += el.offsetWidth - w; break;
+		    case "r": break; // already there
+		}
+		p.width = w;
+		p.height = h + 40;
+		self.monthsCombo.style.display = "none";
+		fixPosition(p);
+		self.showAt(p.x, p.y);
+	};
+	if (Calendar.is_khtml)
+		setTimeout("Calendar.continuation_for_the_khtml_browser()", 10);
+	else
+		Calendar.continuation_for_the_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+	this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+	this.ttDateFormat = str;
+};
+
+/**
+ *  Tries to identify the date represented in a string.  If successful it also
+ *  calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function(str, fmt) {
+	if (!fmt)
+		fmt = this.dateFormat;
+	this.setDate(Date.parseDate(str, fmt));
+};
+
+Calendar.prototype.hideShowCovered = function () {
+	if (!Calendar.is_ie && !Calendar.is_opera)
+		return;
+	function getVisib(obj){
+		var value = obj.style.visibility;
+		if (!value) {
+			if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+				if (!Calendar.is_khtml)
+					value = document.defaultView.
+						getComputedStyle(obj, "").getPropertyValue("visibility");
+				else
+					value = '';
+			} else if (obj.currentStyle) { // IE
+				value = obj.currentStyle.visibility;
+			} else
+				value = '';
+		}
+		return value;
+	};
+
+	var tags = new Array("applet", "iframe", "select");
+	var el = this.element;
+
+	var p = Calendar.getAbsolutePos(el);
+	var EX1 = p.x;
+	var EX2 = el.offsetWidth + EX1;
+	var EY1 = p.y;
+	var EY2 = el.offsetHeight + EY1;
+
+	for (var k = tags.length; k > 0; ) {
+		var ar = document.getElementsByTagName(tags[--k]);
+		var cc = null;
+
+		for (var i = ar.length; i > 0;) {
+			cc = ar[--i];
+
+			p = Calendar.getAbsolutePos(cc);
+			var CX1 = p.x;
+			var CX2 = cc.offsetWidth + CX1;
+			var CY1 = p.y;
+			var CY2 = cc.offsetHeight + CY1;
+
+			if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+				if (!cc.__msh_save_visibility) {
+					cc.__msh_save_visibility = getVisib(cc);
+				}
+				cc.style.visibility = cc.__msh_save_visibility;
+			} else {
+				if (!cc.__msh_save_visibility) {
+					cc.__msh_save_visibility = getVisib(cc);
+				}
+				cc.style.visibility = "hidden";
+			}
+		}
+	}
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+	var fdow = this.firstDayOfWeek;
+	var cell = this.firstdayname;
+	var weekend = Calendar._TT["WEEKEND"];
+	for (var i = 0; i < 7; ++i) {
+		cell.className = "day name";
+		var realday = (i + fdow) % 7;
+		if (i) {
+			cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
+			cell.navtype = 100;
+			cell.calendar = this;
+			cell.fdow = realday;
+			Calendar._add_evs(cell);
+		}
+		if (weekend.indexOf(realday.toString()) != -1) {
+			Calendar.addClass(cell, "weekend");
+		}
+		cell.innerHTML = Calendar._SDN[(i + fdow) % 7];
+		cell = cell.nextSibling;
+	}
+};
+
+/** Internal function.  Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+	this.monthsCombo.style.display = "none";
+	this.yearsCombo.style.display = "none";
+};
+
+/** Internal function.  Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+	if (this.dragging) {
+		return;
+	}
+	this.dragging = true;
+	var posX;
+	var posY;
+	if (Calendar.is_ie) {
+		posY = window.event.clientY + document.body.scrollTop;
+		posX = window.event.clientX + document.body.scrollLeft;
+	} else {
+		posY = ev.clientY + window.scrollY;
+		posX = ev.clientX + window.scrollX;
+	}
+	var st = this.element.style;
+	this.xOffs = posX - parseInt(st.left);
+	this.yOffs = posY - parseInt(st.top);
+	with (Calendar) {
+		addEvent(document, "mousemove", calDragIt);
+		addEvent(document, "mouseup", calDragEnd);
+	}
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR   = 60 * Date.MINUTE;
+Date.DAY    = 24 * Date.HOUR;
+Date.WEEK   =  7 * Date.DAY;
+
+Date.parseDate = function(str, fmt) {
+	var today = new Date();
+	var y = 0;
+	var m = -1;
+	var d = 0;
+	var a = str.split(/\W+/);
+	var b = fmt.match(/%./g);
+	var i = 0, j = 0;
+	var hr = 0;
+	var min = 0;
+	for (i = 0; i < a.length; ++i) {
+		if (!a[i])
+			continue;
+		switch (b[i]) {
+		    case "%d":
+		    case "%e":
+			d = parseInt(a[i], 10);
+			break;
+
+		    case "%m":
+			m = parseInt(a[i], 10) - 1;
+			break;
+
+		    case "%Y":
+		    case "%y":
+			y = parseInt(a[i], 10);
+			(y < 100) && (y += (y > 29) ? 1900 : 2000);
+			break;
+
+		    case "%b":
+		    case "%B":
+			for (j = 0; j < 12; ++j) {
+				if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+			}
+			break;
+
+		    case "%H":
+		    case "%I":
+		    case "%k":
+		    case "%l":
+			hr = parseInt(a[i], 10);
+			break;
+
+		    case "%P":
+		    case "%p":
+			if (/pm/i.test(a[i]) && hr < 12)
+				hr += 12;
+			else if (/am/i.test(a[i]) && hr >= 12)
+				hr -= 12;
+			break;
+
+		    case "%M":
+			min = parseInt(a[i], 10);
+			break;
+		}
+	}
+	if (isNaN(y)) y = today.getFullYear();
+	if (isNaN(m)) m = today.getMonth();
+	if (isNaN(d)) d = today.getDate();
+	if (isNaN(hr)) hr = today.getHours();
+	if (isNaN(min)) min = today.getMinutes();
+	if (y != 0 && m != -1 && d != 0)
+		return new Date(y, m, d, hr, min, 0);
+	y = 0; m = -1; d = 0;
+	for (i = 0; i < a.length; ++i) {
+		if (a[i].search(/[a-zA-Z]+/) != -1) {
+			var t = -1;
+			for (j = 0; j < 12; ++j) {
+				if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+			}
+			if (t != -1) {
+				if (m != -1) {
+					d = m+1;
+				}
+				m = t;
+			}
+		} else if (parseInt(a[i], 10) <= 12 && m == -1) {
+			m = a[i]-1;
+		} else if (parseInt(a[i], 10) > 31 && y == 0) {
+			y = parseInt(a[i], 10);
+			(y < 100) && (y += (y > 29) ? 1900 : 2000);
+		} else if (d == 0) {
+			d = a[i];
+		}
+	}
+	if (y == 0)
+		y = today.getFullYear();
+	if (m != -1 && d != 0)
+		return new Date(y, m, d, hr, min, 0);
+	return today;
+};
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+	var year = this.getFullYear();
+	if (typeof month == "undefined") {
+		month = this.getMonth();
+	}
+	if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+		return 29;
+	} else {
+		return Date._MD[month];
+	}
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+	var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+	var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+	var time = now - then;
+	return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+	var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+	var DoW = d.getDay();
+	d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
+	var ms = d.valueOf(); // GMT
+	d.setMonth(0);
+	d.setDate(4); // Thu in Week 1
+	return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
+};
+
+/** Checks date and time equality */
+Date.prototype.equalsTo = function(date) {
+	return ((this.getFullYear() == date.getFullYear()) &&
+		(this.getMonth() == date.getMonth()) &&
+		(this.getDate() == date.getDate()) &&
+		(this.getHours() == date.getHours()) &&
+		(this.getMinutes() == date.getMinutes()));
+};
+
+/** Set only the year, month, date parts (keep existing time) */
+Date.prototype.setDateOnly = function(date) {
+	var tmp = new Date(date);
+	this.setDate(1);
+	this.setFullYear(tmp.getFullYear());
+	this.setMonth(tmp.getMonth());
+	this.setDate(tmp.getDate());
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+	var m = this.getMonth();
+	var d = this.getDate();
+	var y = this.getFullYear();
+	var wn = this.getWeekNumber();
+	var w = this.getDay();
+	var s = {};
+	var hr = this.getHours();
+	var pm = (hr >= 12);
+	var ir = (pm) ? (hr - 12) : hr;
+	var dy = this.getDayOfYear();
+	if (ir == 0)
+		ir = 12;
+	var min = this.getMinutes();
+	var sec = this.getSeconds();
+	s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+	s["%A"] = Calendar._DN[w]; // full weekday name
+	s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+	s["%B"] = Calendar._MN[m]; // full month name
+	// FIXME: %c : preferred date and time representation for the current locale
+	s["%C"] = 1 + Math.floor(y / 100); // the century number
+	s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+	s["%e"] = d; // the day of the month (range 1 to 31)
+	// FIXME: %D : american date style: %m/%d/%y
+	// FIXME: %E, %F, %G, %g, %h (man strftime)
+	s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+	s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+	s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+	s["%k"] = hr;		// hour, range 0 to 23 (24h format)
+	s["%l"] = ir;		// hour, range 1 to 12 (12h format)
+	s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+	s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+	s["%n"] = "\n";		// a newline character
+	s["%p"] = pm ? "PM" : "AM";
+	s["%P"] = pm ? "pm" : "am";
+	// FIXME: %r : the time in am/pm notation %I:%M:%S %p
+	// FIXME: %R : the time in 24-hour notation %H:%M
+	s["%s"] = Math.floor(this.getTime() / 1000);
+	s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+	s["%t"] = "\t";		// a tab character
+	// FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+	s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+	s["%u"] = w + 1;	// the day of the week (range 1 to 7, 1 = MON)
+	s["%w"] = w;		// the day of the week (range 0 to 6, 0 = SUN)
+	// FIXME: %x : preferred date representation for the current locale without the time
+	// FIXME: %X : preferred time representation for the current locale without the date
+	s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+	s["%Y"] = y;		// year with the century
+	s["%%"] = "%";		// a literal '%' character
+
+	var re = /%./g;
+	if (!Calendar.is_ie5 && !Calendar.is_khtml)
+		return str.replace(re, function (par) { return s[par] || par; });
+
+	var a = str.match(re);
+	for (var i = 0; i < a.length; i++) {
+		var tmp = s[a[i]];
+		if (tmp) {
+			re = new RegExp(a[i], 'g');
+			str = str.replace(re, tmp);
+		}
+	}
+
+	return str;
+};
+
+Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
+Date.prototype.setFullYear = function(y) {
+	var d = new Date(this);
+	d.__msh_oldSetFullYear(y);
+	if (d.getMonth() != this.getMonth())
+		this.setDate(28);
+	this.__msh_oldSetFullYear(y);
+};
+
+// END: DATE OBJECT PATCHES
+
+
+// global object that remembers the calendar
+window._dynarch_popupCalendar = null;
diff --git a/app/cmdline.py b/app/cmdline.py
new file mode 100644
index 0000000..54a7a79
--- /dev/null
+++ b/app/cmdline.py
@@ -0,0 +1,94 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""\
+NetEpi Collection, command line interface.
+
+    help                This help message.
+    dupscan             Scan for duplicate person records
+    export cases        CSV export of case data
+    export form         Export form definition
+    export report       Export report definition
+    importxml           Import XML cases and persons.
+    list forms          List available form definitions
+    list reports        List available report definitions
+    list syndromes      List available syndromes 
+    report              Run a report
+
+Maintenance and debugging commands (you probably won't need to use these):
+    notifymon           Monitor change notification bus.
+    personindex         Rebuild fuzzy person index (typically this is only
+                        needed if the indexing algorithm has changed).
+"""
+
+import sys, os
+sys.path.insert(0, '{{CGITARGET}}')
+try:
+    import casemgr, cocklebur
+except ImportError:
+    sys.exit('Cannot find application modules in {{CGITARGET}}')
+import config
+from cocklebur import dbobj
+
+ourname = os.path.basename(sys.argv[0])
+
+def setflag(name, arg):
+    try:
+        index = sys.argv.index(arg)
+    except ValueError:
+        setattr(config, name, False)
+    else:
+        setattr(config, name, True)
+        del sys.argv[index]
+
+setflag('debug', '-D')
+setflag('tracedb', '-T')
+
+mode = 'help'
+if len(sys.argv) > 1:
+    mode = sys.argv.pop(1)
+if mode == 'help' or mode == '-h' or mode == '--help':
+    if len(sys.argv) > 1:
+        # ... help <mode>
+        mode = sys.argv.pop(1)
+        sys.argv.append('-h')
+    else:
+        # ... help
+        print __doc__
+        sys.exit(0)
+elif mode in ('list', 'export'):
+    if len(sys.argv) > 1:
+        mode = '%s %s' % (mode, sys.argv.pop(1))
+    else:
+        sys.exit('%s what? See "%s help"' % (mode.title(), ourname))
+
+main = None
+try:
+    module = __import__('casemgr.cmdline.' + mode.replace(' ', ''), 
+                        None, None, 'main')
+except ImportError:
+    pass
+else:
+    try:
+        main = module.main
+    except AttributeError:
+        pass
+if main is None:
+    sys.exit('Unknown mode %r. See "%s help"' % (mode, ourname))
+main(sys.argv[1:])
diff --git a/app/formhelpers.js b/app/formhelpers.js
new file mode 100644
index 0000000..a8f7e00
--- /dev/null
+++ b/app/formhelpers.js
@@ -0,0 +1,262 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+/* REQUIRES:
+ *  casemgr/helpers.js
+ */
+
+var form_prefix = 'form_data.';
+var no_disable_index = 99999;
+var skips_by_input = {};
+
+// Scan all questions on the form, and update their styling to reflect their
+// "want_disable" status.
+function disable_input(input) {
+    input.disabled = true;
+    addClass(input, 'disabledq');
+}
+function enable_input(input) {
+    input.disabled = false;
+    rmClass(input, 'disabledq');
+}
+
+// Where multiple inputs share a name, we get some sort of container object,
+// and need to iterate over the members...
+function applyinput(fn, node) {
+    if (!node) return;
+    if (node.disabled === undefined)
+        for (var i = 0; i < node.length; ++i)
+            fn(node[i]);
+    else {
+        fn(node);
+        // Some inputs have a button dynamically added after the input - we
+        // want it's state to reflect the state of the main input.
+        var next = node.nextSibling;
+        if (next && next.type == 'button')
+            fn(next);
+    }
+}
+
+function do_disables() {
+    for (var name in form_questions) {
+        var question = form_questions[name];
+        if (question.disable_index != question.want_disable_index) {
+            // alert('disable ' + question.name + ' index ' + question.want_disable_index);
+            if (question.want_disable_index == 0)
+                addClass(question.node, 'disabledq');
+            else
+                rmClass(question.node, 'disabledq');
+            for (var i = 0; i < question.inputs.length; ++i) {
+                var disable = (i >= question.want_disable_index);
+                var fields = question.inputs[i];
+                for (var j = 0; j < fields.length; ++j)
+                    applyinput(disable ? disable_input : enable_input, 
+                               question.form[form_prefix + fields[j]]);
+            }
+            question.disable_index = question.want_disable_index;
+        }
+    }
+}
+
+function _get_input_value(input) {
+    switch (input.nodeName) {
+    case 'INPUT':
+        switch (input.type) {
+        case 'radio':
+        case 'checkbox':
+            return input.checked ? input.value : null;
+        default:
+            return input.value;
+        }
+        break;
+    case 'OPTION':
+        if (input.selected)
+            return input.value ? input.value : input.text;
+        return null;
+    }
+}
+function get_input_values (input) {
+    if (!input) return;
+    if (input.length) {
+        var values = [];
+        for (var i = 0; i < input.length; ++i) {
+            var v = _get_input_value(input[i]);
+            if (v != null) values.push(v);
+        }
+        return values;
+    }
+    else 
+        return [_get_input_value(input)];
+}
+
+// Clear all question's "want_disable" status.
+function clear_want_disable() {
+    for (var name in form_questions) {
+        var question = form_questions[name];
+        if (question.trigger_mode == 'enable')
+            question.want_disable_index = 0;
+        else
+            question.want_disable_index = no_disable_index;
+    }
+}
+
+// Scan all "skips" and update input-disables to reflect
+function skips_update_disables () {
+    clear_want_disable();
+    for (var j = 0; j < form_skips.length; ++j) {
+        var skip = form_skips[j];
+        if (skip.fired)
+            for (var i = 0; i < skip.targets.length; ++i) {
+                var question = form_questions[skip.targets[i]];
+                var index = (question === skip.question) ? skip.next_input : 0;
+                // alert([skip.name, question.name, question.trigger_mode, skip.question.name, question === skip.question, index, question.want_disable_index].join(' '));
+                if (index == 0 && question.trigger_mode == 'enable')
+                    question.want_disable_index = no_disable_index;
+                else if (index < question.want_disable_index)
+                    question.want_disable_index = index;
+            }
+    }
+}
+
+// An input associated with "skip" has fired - scan all the skips associated
+// with this skip to determine it's status.
+function skip_event (skip) {
+    if (!skip) return;
+    skip.fired = skip.inverted;
+    for (var j = 0; j < skip.inputs.length; ++j) {
+        var input = skip.inputs[j];
+        var input_values = get_input_values(input.elements);
+        // alert('input pending ' + input.name + ' value ' + input_values);
+        if (input_values)
+            for (var i = 0; i < input_values.length; ++i) {
+                var value = input_values[i];
+                if (input.has_value[value]) {
+                    skip.fired = !skip.inverted;
+                    return;
+                }
+            }
+    }
+
+}
+
+var inputs_pending = {};
+var inputs_pending_timer = false;
+
+// At least one input event has been received - update the effecticed skip's
+// state, then recalculate input-disables. 
+function check_pending_inputs () {
+    inputs_pending_timer = false;
+    for (var name in inputs_pending) {
+        var name = name.substr(form_prefix.length);
+        var skips_for_input = skips_by_input[name];
+        for (var i = 0; i < skips_for_input.length; ++i)
+            skip_event(skips_for_input[i]);
+    }
+    inputs_pending = {};
+    skips_update_disables();
+    do_disables();
+}
+
+// An input has fired - make a note, check noted inputs later. We monitor a
+// number of events from every input to ensure we're reliably woken up, so this
+// logic effectively merges the multiple events back into a single per-input
+// event.
+function input_event(ev) {
+    var src = ev.target ? ev.target : ev.srcElement;
+    //alert([src, src.parentNode.name, src.name, src.type].join());
+    if (!src.name) return;
+    inputs_pending[src.name] = true;
+    if (!inputs_pending_timer) {
+        inputs_pending_timer = true;
+        setTimeout(check_pending_inputs, 100);
+    }
+}
+
+function form_question_init () {
+    try {var ignored=form_data_version} catch (e) {form_data_version=0};
+    if (form_data_version != 1) {
+        alert('Your web browser has used an incorrect version of ' +
+              'formhelpers.js - form skips and error jumps are unavailable!');
+        return;
+    }
+    var form = document['appform'];
+    var q_count = 0;
+    var first_error;
+    calendar_init();    // onLoad handlers run in undefined order
+    for (var name in form_questions) {
+        var question = form_questions[name];
+        question.disable_index = no_disable_index;
+        question.form = form;
+        question.node = document.getElementById('Q_' + question.name);
+        question.input_index = {};
+        for (var i = 0; i < question.inputs.length; ++i) {
+            var fields = question.inputs[i];
+            for (var j = 0; j < fields.length; ++j)
+                question.input_index[fields[j]] = i;
+        }
+        if (question.node) {
+            ++q_count;
+            if (!first_error && question.error) first_error = question;
+        }
+    }
+    var w_count = 0;
+    for (var i = 0; i < form_skips.length; ++i) {
+        var skip = form_skips[i];
+        if (!form_questions[skip.question])
+            alert('skip question not found ' + skip.question);
+        skip.question = form_questions[skip.question];
+        skip.next_input = 0;
+        for (var j = 0; j < skip.inputs.length; ++j) {
+            var input = skip.inputs[j];
+            var input_skip_list = skips_by_input[input.name];
+            if (input_skip_list)
+                input_skip_list.push(skip);
+            else
+                skips_by_input[input.name] = [skip];
+            input.elements = form[form_prefix + input.name];
+            input.has_value = {};
+            if (input.elements) {
+                var input_index = skip.question.input_index[input.name];
+//                if (input_index == undefined)
+//                    alert('Input index not found, input ' + input.name);
+                if (skip.next_input < input_index + 1)
+                    skip.next_input = input_index + 1;
+                var binder = function (input) {
+                    if (!input.name) input = input.parentNode;
+                    addEvent(input, 'focus', input_event);
+                    addEvent(input, 'blur', input_event);
+                    addEvent(input, 'change', input_event);
+                    addEvent(input, 'click', input_event);
+                }
+                forEach(input.elements, binder);
+                for (var k = 0; k < input.values.length; ++k)
+                    input.has_value[input.values[k]] = true;
+                ++w_count;
+            }
+//            else
+//                alert('Input elements not found: ' + form_prefix + input.name);
+        }
+        skip_event(skip);
+    }
+    skips_update_disables();
+    do_disables();
+    if (first_error)
+        scrollToElement(first_error.node);
+    //alert('Bound ' + q_count + ' questions, ' + w_count + ' widgets for ' + form_skips.length + ' skips');
+}
+addEvent(window, "load", form_question_init);
diff --git a/app/help/admin_help.html b/app/help/admin_help.html
new file mode 100644
index 0000000..a2f87e0
--- /dev/null
+++ b/app/help/admin_help.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- 
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+<head>
+  <meta http-equiv="CONTENT-TYPE" content="text/html; charset=ISO-8859-1">
+  <title>NetEpi Collection Help File Placeholder</title>
+  <style type="text/css">
+   body {
+    width: 100%;
+    height: 100%;
+    background: white;
+   }
+   .sorry {
+    width: 70%; 
+    text-align: center;
+    border: 2px solid blue;
+    background: #eef;
+    color: black;
+    margin: auto;
+   }
+  </style>
+</head>
+<body lang="en-UK">
+<div class="sorry">
+<h1>Sorry</h1>
+
+<p>Your NetEpi Collection installation currently does not have admin
+documentation installed. Please contact your system administrator.</p>
+
+<p>The documentation can be downloaded from:
+
+<div><a href="http://code.google.com/p/netepi/">http://code.google.com/p/netepi/</a></div>
+</p>
+
+</div>
+</body>
+</html>
diff --git a/app/help/contributors.html b/app/help/contributors.html
new file mode 100644
index 0000000..ed8fcb8
--- /dev/null
+++ b/app/help/contributors.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- 
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+-->
+<html>
+<head>
+  <meta http-equiv="CONTENT-TYPE"
+ content="text/html; charset=ISO-8859-1">
+  <title>Contributors to NetEpi Collection Version 0.15</title>
+</head>
+<body lang="en-UK" dir="">
+<h1>Contributors to NetEpi Collection Version 0.15</h1>
+
+<p>NetEpi Collection was developed by the Centre for Epidemiology and
+Research, New South Wales Department of Health (Health Administration
+Corporation, New South Wales) and by Object Craft Pty Ltd, Melbourne,
+working under contract to the New South Wales Department of Health.</p>
+
+<p>The Australian Government Department of Health and Ageing sponsored the
+following enhancements to version 0.15 of NetEpi Collection:
+<ul>
+<li>Addition of function to export cases/person and case forms as a row
+    per case CSV file (and associated unit tests).</li>
+<li>Addition of new "local case id" case field, re-label "case id" as
+    "system case id".</li>
+<li>Parameterised "syndrome label" -> "Case Definition".</li>
+<li>Miscellaneous cosmetic/presentation changes.<li>
+<li>Miscellaneous bug fixes.</li>
+</ul>
+
+<p>Some third-party programme code and software components, all of which are
+licensed under free, open source licences which are compatible with that used
+by NetEpi Collection, have also been incorporated into NetEpi Collection and
+are included as part of the NetEpi Collection distribution.  Details of these
+inclusions are provided in the <a href='copyright.html'>LICENCE</a> file.
+</body>
+</html>
+
diff --git a/app/help/copyright.html b/app/help/copyright.html
new file mode 100644
index 0000000..9093fca
--- /dev/null
+++ b/app/help/copyright.html
@@ -0,0 +1,622 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <meta http-equiv="content-type"  content="text/html; charset=ISO-8859-1">
+  <title>NetEpi Copyright and Licensing Arrangements</title>
+  <style>
+   <!--
+   body {
+    background-color: white;
+    color: black;
+   }
+   pre {
+    font-family: fixed;
+    font-size: 10pt;
+   }
+   -->
+  </style>
+</head>
+<body bgcolor="white">
+<pre>
+COPYRIGHT AND LICENSING ARRANGEMENTS
+
+All material is copyright 2004-2010 Health Administration Corporation
+(New South Wales Department of Health), Australian Government Department
+of Health and Ageing, and others, except where otherwise indicated in
+the header section of the specific files listed below.
+
+Except for the files listed below, NetEpi Collection is licensed
+under the terms of the Health Administration Corporation Open Source
+Licence V1.2 (HACOS Licence V1.2), the complete text of which appears
+below.
+
+The following files, included in the NetEpi Collection distribution,
+are copyright to the owners and are licensed under the terms indicated
+in the following list. Please see the file header for each of these
+files for further details.
+
+Elements of 'The Coolest DHTML Calendar'
+http://sourceforge.net/projects/jscalendar
+Copyright Mihai Bazon, licensed under the LGPL:
+	casemgr/app/calendar.css
+	casemgr/app/lang/calendar-en.js
+	casemgr/app/calendar.js
+
+"Safe HTML" code
+Copyright Object Craft Pty Ltd, licensed under the 
+Object Craft License (similar to the BSD licence),
+details available from http://www.object-craft.com.au:
+        cocklebur/safehtml.py
+
+Phonetic encoding routines from the FEBRL project
+(a collaborative project between Australian National University
+and the Centre for Epidemiology and Research, NSW Department of Health,
+see http://datamining.anu.edu.au/projects/linkage.html ), under the
+ANUOS License V1.1, which is almost identical to the HACOS V1.2 license
+used by NetEpi Collection:
+        casemgr/phonetic_encode.py
+
+Elements of the Trac system (enhanced wiki and issue tracking system for
+software development projects) by Edgewall Software (see 
+http://trac.edgewall.org/ ), licensed under the Trac licence (a modified 
+BSD licence), the full text of which can be found in the Trac_licence.txt
+file included in the this NetEpi Collection distribution. The files
+covered by the Trac licence are:
+        wiki/api.py
+	wiki/core.py
+	wiki/env.py
+	wiki/formatter.py
+	wiki/href.py
+	wiki/log.py
+	wiki/util.py
+	app/app/wiki.css
+
+The Javascript code used to control the behaviour of the browser "Back"
+button  was initially inspired by the dojo.io.bind() function in the
+Dojo Javascript toolkit (http://www.dojotoolkit.org/), as described
+in more detail by Alex Russell at http://alex.dojotoolkit.org/?p=479.
+However, no Javascript code from the Dojo toolkit was re-used - all
+code was re-written de novo. Brad Neuberg's notes about the underlying
+technique at
+http://codinginparadise.org/weblog/2005/08/ajax-tutorial-tale-of-two-iframes-or.html
+were also found to be useful, but again no Javascript code was re-used
+or borrowed. The relevant file is:
+        app/app/nobbleback.js	
+	
+----------------------------------------------------------------	
+HEALTH ADMINISTRATION CORPORATION OPEN SOURCE LICENCE VERSION 1.2
+
+1. DEFINITIONS. 
+
+   "Commercial Use" shall mean distribution or otherwise making the
+   Covered Software available to a third party.
+
+   "Contributor" shall mean each entity that creates or contributes to
+   the creation of Modifications.
+
+   "Contributor Version" shall mean in case of any Contributor the
+   combination of the Original Software, prior Modifications used by a
+   Contributor, and the Modifications made by that particular Contributor
+   and in case of Health Administration Corporation in addition the
+   Original Software in any form, including the form as Executable.
+
+   "Covered Software" shall mean the Original Software or Modifications
+   or the combination of the Original Software and Modifications, in
+   each case including portions thereof.
+
+   "Electronic Distribution Mechanism" shall mean a mechanism generally
+   accepted in the software development community for the electronic
+   transfer of data.
+
+   "Executable" shall mean Covered Software in any form other than
+   Source Code.
+
+   "Initial Developer" shall mean the individual or entity identified as
+   the Initial Developer in the Source Code notice required by Exhibit A.
+
+   "Health Administration Corporation" shall mean the Health
+   Administration Corporation as established by the Health Administration
+   Act 1982, as amended, of the State of New South Wales, Australia. The
+   Health Administration Corporation has its offices at 73 Miller Street,
+   North Sydney, New South Wales 2059, Australia.
+
+   "Larger Work" shall mean a work, which combines Covered Software or
+   portions thereof with code not governed by the terms of this Licence.
+
+   "Licence" shall mean this document.
+
+   "Licensable" shall mean having the right to grant, to the maximum
+   extent possible, whether at the time of the initial grant or
+   subsequently acquired, any and all of the rights conveyed herein.
+
+   "Modifications" shall mean any addition to or deletion from the
+   substance or structure of either the Original Software or any previous
+   Modifications. When Covered Software is released as a series of files,
+   a Modification is:
+
+   a) Any addition to or deletion from the contents of a file
+      containing Original Software or previous Modifications.
+
+   b) Any new file that contains any part of the Original Software or
+      previous Modifications.
+
+   "Original Software" shall mean the Source Code of computer software
+   code which is described in the Source Code notice required by Exhibit
+   A as Original Software, and which, at the time of its release under
+   this Licence is not already Covered Software governed by this Licence.
+
+   "Patent Claims" shall mean any patent claim(s), now owned or hereafter
+   acquired, including without limitation, method, process, and apparatus
+   claims, in any patent Licensable by grantor.
+
+   "Source Code" shall mean the preferred form of the Covered Software
+   for making modifications to it, including all modules it contains,
+   plus any associated interface definition files, scripts used to
+   control compilation and installation of an Executable, or source
+   code differential comparisons against either the Original Software or
+   another well known, available Covered Software of the Contributor's
+   choice. The Source Code can be in a compressed or archival form,
+   provided the appropriate decompression or de-archiving software is
+   widely available for no charge.
+
+   "You" (or "Your") shall mean an individual or a legal entity exercising
+   rights under, and complying with all of the terms of, this Licence or
+   a future version of this Licence issued under Section 6.1. For legal
+   entities, "You" includes an entity which controls, is controlled
+   by, or is under common control with You. For the purposes of this
+   definition, "control" means (a) the power, direct or indirect,
+   to cause the direction or management of such entity, whether by
+   contract or otherwise, or (b) ownership of more than fifty per cent
+   (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. SOURCE CODE LICENCE. 
+
+2.1 Health Administration Corporation Grant. 
+
+Subject to the terms of this Licence, Health Administration Corporation
+hereby grants You a world-wide, royalty-free, non-exclusive licence,
+subject to third party intellectual property claims:
+
+a) under copyrights Licensable by Health Administration Corporation
+   to use, reproduce, modify, display, perform, sublicense and
+   distribute the Original Software (or portions thereof) with or without
+   Modifications, and/or as part of a Larger Work;
+
+b) and under Patents Claims infringed by the making, using or selling
+   of Original Software, to make, have made, use, practice, sell, and
+   offer for sale, and/or otherwise dispose of the Original Software
+   (or portions thereof).
+
+c) The licences granted in this Section 2.1(a) and (b) are effective
+   on the date Health Administration Corporation first distributes
+   Original Software under the terms of this Licence.
+
+d) Notwithstanding Section 2.1(b) above, no patent licence is granted:
+   1) for code that You delete from the Original Software; 2) separate
+   from the Original Software; or 3) for infringements caused by: i)
+   the modification of the Original Software or ii) the combination of
+   the Original Software with other software or devices.
+
+2.2 Contributor Grant. 
+
+Subject to the terms of this Licence and subject to third party
+intellectual property claims, each Contributor hereby grants You a
+world-wide, royalty-free, non-exclusive licence:
+
+a) under copyrights Licensable by Contributor, to use, reproduce,
+   modify, display, perform, sublicense and distribute the Modifications
+   created by such Contributor (or portions thereof) either on an
+   unmodified basis, with other Modifications, as Covered Software and/or
+   as part of a Larger Work; and
+
+b) under Patent Claims necessarily infringed by the making, using,
+   or selling of Modifications made by that Contributor either alone
+   and/or in combination with its Contributor Version (or portions of
+   such combination), to make, use, sell, offer for sale, have made,
+   and/or otherwise dispose of: 1) Modifications made by that Contributor
+   (or portions thereof); and 2) the combination of Modifications made
+   by that Contributor with its Contributor Version (or portions of
+   such combination).
+
+c) The licences granted in Sections 2.2(a) and 2.2(b) are effective
+   on the date Contributor first makes Commercial Use of the Covered
+   Software.
+
+d) Notwithstanding Section 2.2(b) above, no patent licence is granted:
+   1) for any code that Contributor has deleted from the Contributor
+   Version; 2) separate from the Contributor Version; 3) for infringements
+   caused by: i) third party modifications of Contributor Version or ii)
+   the combination of Modifications made by that Contributor with other
+   software (except as part of the Contributor Version) or other devices;
+   or 4) under Patent Claims infringed by Covered Software in the absence
+   of Modifications made by that Contributor.
+
+3. DISTRIBUTION OBLIGATIONS. 
+
+3.1 Application of Licence. 
+
+The Modifications which You create or to which You contribute are governed
+by the terms of this Licence, including without limitation Section
+2.2. The Source Code version of Covered Software may be distributed
+only under the terms of this Licence or a future version of this Licence
+released under Section 6.1, and You must include a copy of this Licence
+with every copy of the Source Code You distribute. You may not offer or
+impose any terms on any Source Code version that alters or restricts the
+applicable version of this Licence or the recipients' rights hereunder.
+
+3.2 Availability of Source Code. 
+
+Any Modification which You create or to which You contribute must be made
+available in Source Code form under the terms of this Licence either on
+the same media as an Executable version or via an accepted Electronic
+Distribution Mechanism to anyone to whom you made an Executable version
+available; and if made available via Electronic Distribution Mechanism,
+must remain available for at least twelve (12) months after the date it
+initially became available, or at least six (6) months after a subsequent
+version of that particular Modification has been made available to
+such recipients. You are responsible for ensuring that the Source Code
+version remains available even if the Electronic Distribution Mechanism
+is maintained by a third party.
+
+3.3 Description of Modifications. 
+
+You must cause all Covered Software to which You contribute to contain
+a file documenting the changes You made to create that Covered Software
+and the date of any change. You must include a prominent statement that
+the Modification is derived, directly or indirectly, from Original
+Software provided by Health Administration Corporation and including
+the name of Health Administration Corporation in (a) the Source Code,
+and (b) in any notice in an Executable version or related documentation
+in which You describe the origin or ownership of the Covered Software.
+
+3.4 Intellectual Property Matters 
+
+a) Third Party Claims. 
+
+   If Contributor has knowledge that a licence under a third party's
+   intellectual property rights is required to exercise the rights
+   granted by such Contributor under Sections 2.1 or 2.2, Contributor
+   must include a text file with the Source Code distribution titled
+   "LEGAL'' which describes the claim and the party making the claim
+   in sufficient detail that a recipient will know whom to contact. If
+   Contributor obtains such knowledge after the Modification is made
+   available as described in Section 3.2, Contributor shall promptly
+   modify the LEGAL file in all copies Contributor makes available
+   thereafter and shall take other steps (such as notifying appropriate
+   mailing lists or newsgroups) reasonably calculated to inform those
+   who received the Covered Software that new knowledge has been obtained.
+
+b) Contributor APIs. 
+
+   If Contributor's Modifications include an application programming
+   interface (API) and Contributor has knowledge of patent licences
+   which are reasonably necessary to implement that API, Contributor
+   must also include this information in the LEGAL file.
+
+c) Representations. 
+
+   Contributor represents that, except as disclosed pursuant to Section
+   3.4(a) above, Contributor believes that Contributor's Modifications are
+   Contributor's original creation(s) and/or Contributor has sufficient
+   rights to grant the rights conveyed by this Licence.
+
+3.5 Required Notices. 
+
+You must duplicate the notice in Exhibit A in each file of the Source
+Code. If it is not possible to put such notice in a particular Source
+Code file due to its structure, then You must include such notice in a
+location (such as a relevant directory) where a user would be likely to
+look for such a notice. If You created one or more Modification(s) You
+may add your name as a Contributor to the notice described in Exhibit
+A. You must also duplicate this Licence in any documentation for the
+Source Code where You describe recipients' rights or ownership rights
+relating to Covered Software. You may choose to offer, and to charge a
+fee for, warranty, support, indemnity or liability obligations to one or
+more recipients of Covered Software. However, You may do so only on Your
+own behalf, and not on behalf of Health Administration Corporation or any
+Contributor. You must make it absolutely clear that any such warranty,
+support, indemnity or liability obligation is offered by You alone,
+and You hereby agree to indemnify Health Administration Corporation and
+every Contributor for any liability incurred by Health Administration
+Corporation or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+3.6 Distribution of Executable Versions. 
+
+You may distribute Covered Software in Executable form only if the
+requirements of Sections 3.1-3.5 have been met for that Covered Software,
+and if You include a notice stating that the Source Code version of the
+Covered Software is available under the terms of this Licence, including
+a description of how and where You have fulfilled the obligations of
+Section 3.2. The notice must be conspicuously included in any notice in
+an Executable version, related documentation or collateral in which You
+describe recipients' rights relating to the Covered Software. You may
+distribute the Executable version of Covered Software or ownership rights
+under a licence of Your choice, which may contain terms different from
+this Licence, provided that You are in compliance with the terms of this
+Licence and that the licence for the Executable version does not attempt
+to limit or alter the recipient's rights in the Source Code version from
+the rights set forth in this Licence. If You distribute the Executable
+version under a different licence You must make it absolutely clear
+that any terms which differ from this Licence are offered by You alone,
+not by Health Administration Corporation or any Contributor. You hereby
+agree to indemnify Health Administration Corporation and every Contributor
+for any liability incurred by Health Administration Corporation or such
+Contributor as a result of any such terms You offer.
+
+3.7 Larger Works. 
+
+You may create a Larger Work by combining Covered Software with other
+software not governed by the terms of this Licence and distribute the
+Larger Work as a single product. In such a case, You must make sure the
+requirements of this Licence are fulfilled for the Covered Software.
+
+4. INABILITY TO COMPLY DUE TO STATUTE OR REGULATION. 
+
+If it is impossible for You to comply with any of the terms of this
+Licence with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with the
+terms of this Licence to the maximum extent possible; and (b) describe the
+limitations and the code they affect. Such description must be included
+in the LEGAL file described in Section 3.4 and must be included with all
+distributions of the Source Code.  Except to the extent prohibited by
+statute or regulation, such description must be sufficiently detailed
+for a recipient of ordinary skill to be able to understand it.
+
+5. APPLICATION OF THIS LICENCE. 
+
+This Licence applies to code to which Health Administration Corporation
+has attached the notice in Exhibit A and to related Covered Software.
+
+6. VERSIONS OF THE LICENCE. 
+
+6.1 New Versions. 
+
+Health Administration Corporation may publish revised and/or new
+versions of the Licence from time to time. Each version will be given
+a distinguishing version number.
+
+6.2 Effect of New Versions. 
+
+Once Covered Software has been published under a particular version
+of the Licence, You may always continue to use it under the terms of
+that version. You may also choose to use such Covered Software under
+the terms of any subsequent version of the Licence published by Health
+Administration Corporation. No one other than Health Administration
+Corporation has the right to modify the terms applicable to Covered
+Software created under this Licence.
+
+7. DISCLAIMER OF WARRANTY. 
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENCE ON AN "AS IS'' BASIS,
+WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS
+WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU
+(NOT HEALTH ADMINISTRATION CORPORATION, ITS LICENSORS OR AFFILIATES OR
+ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR
+OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
+OF THIS LICENCE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER
+EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION. 
+
+8.1 This Licence and the rights granted hereunder will terminate
+automatically if You fail to comply with terms herein and fail to
+cure such breach within 30 days of becoming aware of the breach. All
+sublicences to the Covered Software which are properly granted shall
+survive any termination of this Licence. Provisions which, by their
+nature, must remain in effect beyond the termination of this Licence
+shall survive.
+
+8.2 If You initiate litigation by asserting a patent infringement claim
+(excluding declatory judgment actions) against Health Administration
+Corporation or a Contributor (Health Administration Corporation
+or Contributor against whom You file such action is referred to as
+"Participant") alleging that:
+
+a) such Participant's Contributor Version directly or indirectly
+   infringes any patent, then any and all rights granted by such
+   Participant to You under Sections 2.1 and/or 2.2 of this Licence
+   shall, upon 60 days notice from Participant terminate prospectively,
+   unless if within 60 days after receipt of notice You either: (i)
+   agree in writing to pay Participant a mutually agreeable reasonable
+   royalty for Your past and future use of Modifications made by such
+   Participant, or (ii) withdraw Your litigation claim with respect to
+   the Contributor Version against such Participant. If within 60 days
+   of notice, a reasonable royalty and payment arrangement are not
+   mutually agreed upon in writing by the parties or the litigation
+   claim is not withdrawn, the rights granted by Participant to
+   You under Sections 2.1 and/or 2.2 automatically terminate at the
+   expiration of the 60 day notice period specified above.
+
+b) any software, hardware, or device, other than such Participant's
+   Contributor Version, directly or indirectly infringes any patent,
+   then any rights granted to You by such Participant under Sections
+   2.1(b) and 2.2(b) are revoked effective as of the date You first
+   made, used, sold, distributed, or had made, Modifications made by
+   that Participant.
+
+8.3 If You assert a patent infringement claim against Participant
+alleging that such Participant's Contributor Version directly or
+indirectly infringes any patent where such claim is resolved (such as by
+licence or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licences granted by such
+Participant under Sections 2.1 or 2.2 shall be taken into account in
+determining the amount or value of any payment or license.  
+
+8.4 In the event of termination under Sections 8.1 or 8.2 above, all
+end user licence agreements (excluding distributors and resellers) which
+have been validly granted by You or any distributor hereunder prior to
+termination shall survive termination.
+
+9. LIMITATION OF LIABILITY. 
+
+9.1 UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, HEALTH
+ADMINISTRATION CORPORATION, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR
+OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE
+TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
+ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE
+BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, BUT MAY ALLOW
+LIABILITY TO BE LIMITED; IN SUCH CASES, A PARTY'S, ITS EMPLOYEES',
+LICENSORS' OR AFFILIATES' LIABILITY SHALL BE LIMITED TO AUD$100. NOTHING
+CONTAINED IN THIS LICENCE SHALL PREJUDICE THE STATUTORY RIGHTS OF ANY
+PARTY DEALING AS A CONSUMER.
+
+9.2 Notwithstanding any other clause in the licence, and to the extent
+permitted by law:
+
+(a) Health Administration Corporation ("the Corporation") excludes all
+    conditions and warranties which would otherwise be implied into
+    a supply of goods or services arising out of or in relation to
+    the granting of this licence by the Corporation or any associated
+    acquisition of software to which this licence relates;
+
+(b) Where a condition or warranty is implied into such a supply and
+    that condition or warranty cannot be excluded by law that warranty
+    or condition is implied into that supply and the liability of the
+    Health Administration Corporation for a breach of that condition or
+    warranty is limited to the fullest extent permitted by law and, in
+    respect of conditions and warranties implied by the Trade Practices
+    Act (Commonwealth of Australia) 1974, is limited, to the extent
+    permitted by law, to one or more of the following at the election
+    of the Corporation:
+
+    (A) In the case of goods: (i) the replacement of the goods or the
+        supply of equivalent goods; (ii) the repair of the goods; (iii)
+        the payment of the cost of replacing the goods or of acquiring
+        equivalent goods; (iv) the payment of the cost of having the
+        goods repaired; and
+
+    (B) in the case of services: (i) the supplying of the services again;
+        or (ii) the payment of the cost of having the services supplied
+        again.
+
+10. MISCELLANEOUS. 
+
+This Licence represents the complete agreement concerning subject matter
+hereof. All rights in the Covered Software not expressly granted under
+this Licence are reserved. Nothing in this Licence shall grant You any
+rights to use any of the trademarks of Health Administration Corporation
+or any of its Affiliates, even if any of such trademarks are included
+in any part of Covered Software and/or documentation to it.
+
+This Licence is governed by the laws of the State of New South Wales,
+Australia excluding its conflict-of-law provisions. All disputes or
+litigation arising from or relating to this Agreement shall be subject
+to the jurisdiction of the Supreme Court of New South Wales. If any part
+of this Agreement is found void and unenforceable, it will not affect
+the validity of the balance of the Agreement, which shall remain valid
+and enforceable according to its terms.
+
+11. RESPONSIBILITY FOR CLAIMS. 
+
+As between Health Administration Corporation and the Contributors,
+each party is responsible for claims and damages arising, directly or
+indirectly, out of its utilisation of rights under this Licence and You
+agree to work with Health Administration Corporation and Contributors
+to distribute such responsibility on an equitable basis. Nothing herein
+is intended or shall be deemed to constitute any admission of liability.
+
+EXHIBIT A 
+
+The contents of this file are subject to the HACOS Licence Version 1.2
+(the "Licence"); you may not use this file except in compliance with
+the Licence.
+
+Software distributed under the Licence is distributed on an "AS IS"
+basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+Licence for the specific language governing rights and limitations under
+the Licence.
+
+The Original Software is "NetEpi Collection". The Initial Developer
+of the Original Software is the Health Administration Corporation,
+incorporated in the State of New South Wales, Australia.
+
+Copyright (C) 2004-2011 Health Administration Corporation, Australian
+Government Department of Health and Ageing, and others. All Rights
+Reserved.
+Contributors: See the CONTRIBUTORS file for details of contributions.
+
+APPENDIX 1. DIFFERENCES BETWEEN THE HACOS LICENCE VERSION 1.2, THE
+MOZILLA PUBLIC LICENSE VERSION 1.1 AND THE NOKIA OPEN SOURCE LICENSE
+(NOKOS LICENSE) VERSION 1.0A
+
+The HACOS Licence Version 1.2 was derived from the Mozilla Public
+License Version 1.1 using some of the changes to the Mozilla Public
+License embodied in the Nokia Open Source License (NOKOS License)
+Version 1.0a. The differences between the HACOS Licence Version 1.2
+(this document), the Mozilla Public License and the NOKOS License are
+as follows:
+
+i.    The title of the licence was changed to "Health Administration
+      Corporation Open Source Licence Version 1.2".
+
+ii.   Globally, all references to "Netscape Communications Corporation",
+      "Mozilla", "Nokia" and "Nokia Corporation" were changed to "Health
+      Administration Corporation".
+
+iii.  Globally, the words "means", "Covered Code" and "Covered Software"
+      as used in the Mozilla Public License were changed to "shall means",
+      "Covered Code" and "Covered Software" respectively, as used in
+      the NOKOS License.
+
+iv.   In Section 1 (Definitions), a definition of "Health Administration
+      Corporation" was added.
+
+v.    In Section 2, the term "intellectual property rights" used in the
+      Mozilla Public License was replaced by the term "copyrights"
+      as used in the NOKOS License.
+
+vi.   In Section 2.2 (Contributor Grant), the words "Subject to the
+      terms of this License" which appear in the NOKOS License were
+      added to the Mozilla Public License.
+
+vii.  The sentence "However, You may include an additional document
+      offering the additional rights described in Section 3.5." which
+      appears in the Mozilla Public License was omitted.
+
+viii. Section 6.3 (Derivative Works) of the Mozilla Public License,
+      which permits modifications to the Mozilla Public License,
+      was omitted.
+
+ix.   The original Section 9 (Limitation of Liability) was renumbered
+      as Section 9.1, a maximum liability of AUD$100 was specified
+      for those jurisdictions which do not allow complete exclusion of
+      liability but which do allow limitation of liability. The sentence
+      "NOTHING CONTAINED IN THE LICENSE SHALL PREJUDICE THE STATUTORY
+      RIGHTS OF ANY PARTY DEALING AS A CONSUMER.", which appears in the
+      NOKOS License but not in the Mozilla Public License, was added.
+
+x.    Section 9.2 was added in order to further limit liability to the
+      maximum extent permitted by the Commonwealth of Australia Trade
+      Practices Act 1974.
+
+xi.   Section 10 of the Mozilla Public License, which provides additional
+      conditions for United States Government End Users, was omitted.
+
+xii.  The governing law and jurisdiction for the settlement of disputes
+      in Section 11 of the Mozilla Public License and Section 10 of the
+      NOKOS License was changed to the laws of the State of New South
+      Wales and the Supreme Court of New South Wales respectively. The
+      exclusion of the application of the United Nations Convention on
+      Contracts for the International Sale of Goods which appears in
+      the Mozilla Public License was omitted.
+
+xiii. Section 13 (Multiple-Licensed Code) of the Mozilla Public License 
+      was omitted.
+
+xiv.  The provisions for alternative licensing arrangement for contributed
+      code which appear in Exhibit A of the Mozilla Public License 
+      were omitted.
+</pre>
+</body>
+</html>
diff --git a/app/help/help.html b/app/help/help.html
new file mode 100644
index 0000000..e153cfc
--- /dev/null
+++ b/app/help/help.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- 
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+<head>
+  <meta http-equiv="CONTENT-TYPE" content="text/html; charset=ISO-8859-1">
+  <title>NetEpi Collection Help File Placeholder</title>
+  <style type="text/css">
+   body {
+    width: 100%;
+    height: 100%;
+    background: white;
+   }
+   .sorry {
+    width: 70%; 
+    text-align: center;
+    border: 2px solid blue;
+    background: #eef;
+    color: black;
+    margin: auto;
+   }
+  </style>
+</head>
+<body lang="en-UK">
+<div class="sorry">
+<h1>Sorry</h1>
+
+<p>Your NetEpi Collection installation currently does not have documentation
+installed. Please contact your system administrator.</p>
+
+<p>The documentation can be downloaded from:
+
+<div><a href="http://code.google.com/p/netepi/">http://code.google.com/p/netepi/</a></div>
+</p>
+
+</div>
+</body>
+</html>
diff --git a/app/help/index.html b/app/help/index.html
new file mode 100644
index 0000000..6252f97
--- /dev/null
+++ b/app/help/index.html
@@ -0,0 +1,21 @@
+<!--
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+-->
+<html>
+<body bgcolor="white">
+</body>
+</html>
diff --git a/app/help/wiki_help.html b/app/help/wiki_help.html
new file mode 100644
index 0000000..234033d
--- /dev/null
+++ b/app/help/wiki_help.html
@@ -0,0 +1,235 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!-- 
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->    
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+ <title>NetEpi Wiki Formatting</title>
+ <link rel="stylesheet" href="../app/app/wiki.css" type="text/css" />
+ </head>
+<body>
+<h1 id="WikiFormatting">Wiki Formatting</h1>
+<p>
+NetEpi Collection supports the use of wiki mark-up to indicate how text used in certain parts of the application should be formatted when displayed. Wiki mark-up can be used in admin application for the following items:
+</p>
+<ul><li>Case Definition description text (but not in the Case Definition name)
+</li><li>Bulletin detail text (but in the Bulletin title or synopsis)
+</li><li>in Form definitions, in:
+<ul><li>the form title
+</li><li>form section and sub-section headings
+</li><li>question text
+</li><li>question help text
+</li><li>input pre- and post-text (mark-up relevant to single lines of text only)
+</li><li>choice labels for CheckBoxes and RadioButton inputs (mark-up relevant to single lines of text only)
+</li></ul></li></ul><p>
+The wiki mark-up (formatting) rules are derived from those used in Trac (<a class="ext-link" href="http://trac.edgewall.com/"><span class="icon"></span>http://trac.edgewall.com/</a>), with some minor variations, as documented below.
+</p>
+<h2 id="FontStyles">Font Styles</h2>
+<p>
+NetEpi wiki text which is marked-up thus:
+</p>
+<pre class="wiki"> * *bold*
+ * _italic_
+ * **bold italic**
+ * __underline__
+ * {{{monospace}}} or `monospace`
+ * ~~strike-through~~
+ * ^superscript^ 
+ * ,,subscript,,
+</pre>
+<p>
+Results in text displayed thus:
+</p>
+<ul><li><strong>bold</strong>
+</li><li><i>italic</i>
+</li><li><strong><i>bold italic</i></strong>
+</li><li><span class="underline">underline</span>
+</li><li><tt>monospace</tt> or <tt>monospace</tt>
+</li><li><del>strike-through</del>
+</li><li><sup>superscript</sup> 
+</li><li><sub>subscript</sub>
+</li></ul><p>
+Note that the <tt>{{{...}}}</tt> and <tt>`...`</tt> commands not only select a monospace font, but also treat their content as verbatim text, meaning that no further wiki processing is done on this text.
+</p>
+<h2 id="Headings">Headings</h2>
+<p>
+You can create heading by starting a line with one up to five <i>equal</i> characters ("=")
+followed by a single space and the headline text. The line should end with a space 
+followed by the same number of <i>=</i> characters.
+</p>
+<p>
+Example:
+</p>
+<pre class="wiki">= Heading =
+== Subheading ==
+=== About ''this'' ===
+</pre>
+<p>
+Display:
+</p>
+<h1 id="Heading">Heading</h1>
+<h2 id="Subheading">Subheading</h2>
+<h3 id="Aboutthis">About <i>this</i></h3>
+<h2 id="Paragraphs">Paragraphs</h2>
+<p>
+A new text paragraph is created whenever two blocks of text are separated by one or more empty lines.
+</p>
+<p>
+Unlike Trac, forced line breaks cannot currently be inserted using the <br /> macro tag. Support for this may be added in a future version.
+</p>
+<h2 id="Lists">Lists</h2>
+<p>
+The wiki mark-up supports both ordered/numbered and unordered lists.
+</p>
+<p>
+Example:
+</p>
+<pre class="wiki"> * Item 1
+   * Item 1.1
+ * Item 2
+
+ 1. Item 1
+   1. Item 1.1
+ 1. Item 2
+</pre>
+<p>
+Display:
+</p>
+<ul><li>Item 1
+<ul><li>Item 1.1
+</li></ul></li><li>Item 2
+</li></ul><ol><li>Item 1
+<ol><li>Item 1.1
+</li></ol></li><li>Item 2
+</li></ol><p>
+Note that there must be one or more spaces preceding the list item markers, otherwise the list will be treated as a normal paragraph.
+</p>
+<h2 id="DefinitionLists">Definition Lists</h2>
+<p>
+The wiki also supports definition lists.
+</p>
+<p>
+Example:
+</p>
+<pre class="wiki"> llama::
+   some kind of mammal, with hair
+ ppython::
+   some kind of reptile, without hair
+   (can you spot the typo?)
+</pre>
+<p>
+Display:
+<dl><dt>llama</dt><dd>
+some kind of mammal, with hair
+</dd><dt>ppython</dt><dd>
+some kind of reptile, without hair
+(can you spot the typo?)
+</p>
+</dd></dl>
+<p>
+Note that you need a space in front of the defined term.
+</p>
+<h2 id="PreformattedText">Preformatted Text</h2>
+<p>
+Blocks containing preformatted text can be used for notes and examples or other literal text. Use three <i>curly braces</i> wrapped around the text to define a block quote. The curly braces need to be on a separate line.
+  
+Example:
+</p>
+<pre class="wiki"> {{{
+  def HelloWorld():
+      print "Hello World"
+ }}}
+</pre>
+<p>
+Display:
+</p>
+<pre class="wiki"> def HelloWorld():
+     print "Hello World"
+</pre>
+<h2 id="Blockquotes">Blockquotes</h2>
+<p>
+In order to mark a paragraph as blockquote, indent that paragraph with two spaces.
+</p>
+<p>
+Example:
+</p>
+<pre class="wiki">  This text is a quote from someone else.
+</pre>
+<p>
+Display:
+</p>
+<blockquote>
+<p>
+This text is a quote from someone else.
+</p>
+</blockquote>
+<h2 id="Tables">Tables</h2>
+<p>
+Simple tables can be created like this:
+</p>
+<pre class="wiki">||Cell 1||Cell 2||Cell 3||
+||Cell 4||Cell 5||Cell 6||
+</pre>
+<p>
+Display:
+</p>
+<table class="wiki">
+<tr><td>Cell 1</td><td>Cell 2</td><td>Cell 3
+</td></tr><tr><td>Cell 4</td><td>Cell 5</td><td>Cell 6
+</td></tr></table>
+<p>
+There is currently no support for more complex tables to be created using Restructured Text mark-up. This may be added in a future version.
+</p>
+<h2 id="Links">Links</h2>
+<p>
+No linking is currently supported occur.
+</p>
+<h3 id="TracLinks">Trac Links</h3>
+<p>
+Later we may add support for linking to other Collection elements.
+</p>
+<h2 id="Images">Images</h2>
+<p>
+Support for embedded images will be added in a future version (ticket <a class="new ticket" href="/netepi/ticket/12" title="Allow upload of graphics and other resources for use in forms (new)">#12</a>).
+</p>
+<h2 id="Macros">Macros</h2>
+<p>
+By preserving the Trac macro mechanism we may be able to add support for a range of Trac macros in a future version.
+</p>
+<h2 id="Processors">Processors</h2>
+<p>
+See the comments on Macros.
+</p>
+<h2 id="Miscellaneous">Miscellaneous</h2>
+<p>
+Four or more dashes will be replaced by a horizontal line (<HR>)
+</p>
+<p>
+Example:
+</p>
+<pre class="wiki"> ----
+</pre>
+<p>
+Display:
+</p>
+<hr />
+</body>
+</html>
+
diff --git a/app/helpers.js b/app/helpers.js
new file mode 100644
index 0000000..3b1e35e
--- /dev/null
+++ b/app/helpers.js
@@ -0,0 +1,824 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+/*global window Calendar */
+
+var isOldIE = navigator.userAgent.search(/MSIE [56]/) >= 0;
+
+// Monkey patch prehistoric versions of IE
+if (!Array.prototype.indexOf) {
+    Array.prototype.indexOf = function (obj, start) {
+        for (var i = (start || 0); i < this.length; i++) {
+            if (this[i] === obj) {
+                return i;
+            }
+        }
+        return -1;
+    };
+}
+
+
+//////
+// Structural helpers
+
+function alertargs() {
+    alert(Array.prototype.join.call(arguments, '/'));
+}
+
+var debug_status_win;
+function debugStatus(msg) {
+    if (!debug_status_win) {
+        debug_status_win = document.createElement('div');
+        debug_status_win.style.position = 'fixed';
+        debug_status_win.style.top = '0px';
+        debug_status_win.style.right = '0px';
+        debug_status_win.style.height = '10ex';
+        debug_status_win.style.width = '20em';
+        debug_status_win.style.backgroundColor = '#cfc';
+        debug_status_win.style.opacity = '0.5';
+        document.body.insertBefore(debug_status_win, document.body.firstChild);
+    }
+    debug_status_win.innerHTML = msg;
+}
+
+function forEach(list, fn) {
+    if (!list) {
+        return;
+    }
+    if (list.length) {
+        for (var i = 0; i < list.length; ++i) {
+            fn(list[i]);
+        }
+    } else {
+        fn(list);
+    }
+}
+
+function distance(x1, y1, x2, y2) 
+{
+    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
+}
+
+
+//////
+// Event helpers
+
+function addEvent(o, eventName, handler) {
+    if (o.addEventListener) {
+        o.addEventListener(eventName, handler, false);
+    } else {
+        o.attachEvent("on" + eventName, handler);
+    }
+}
+
+function delEvent(o, eventName, handler) {
+    if (o.removeEventListener) {
+        o.removeEventListener(eventName, handler, false);
+    } else {
+        o.detachEvent("on" + eventName, handler);
+    }
+}
+
+function cancelEvent(ev) {
+    if (ev.preventDefault) {
+        ev.preventDefault();
+    } else {
+        ev.returnValue = false;
+    }
+    if (ev.stopPropagation) {
+        ev.stopPropagation();
+    } else {
+        ev.cancelBubble = true;
+    }
+    return false;
+}
+
+function clickOn(node) {
+    if (node.dispatchEvent) {
+        var ev = document.createEvent("MouseEvents");
+        ev.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, 
+                            false, false, false, false, 0, null);
+        node.dispatchEvent(ev);
+    } else {
+        node.fireEvent('onclick');
+    }
+}
+    
+
+//////
+// DOM helpers
+
+function isChild(node, child) {
+    do {
+        if (node === child.parentNode) {
+            return true;
+        }
+    } while ((child = child.parentNode));
+    return false;
+}
+
+/* iterate down the node tree looking for a node that satisfies condition() */
+function findChild(node, condition, action) {
+    if (condition(node) && action(node)) {
+        return true;
+    }
+    for (var i = 0; i < node.childNodes.length; ++i) {
+        if (findChild(node.childNodes[i], condition, action)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// insert /next/ after /node/
+function appendAfter(next, node) {
+    if (node.nextSibling) {
+        node.parentNode.insertBefore(next, node.nextSibling);
+    } else {
+        node.parentNode.appendChild(next);
+    }
+}
+
+/* iterate back up the tree looking for a parent of /node/ that is of type
+ * /tagName/
+ */
+function parentTag(node, tagName) {
+    while (node) {
+        if (node.nodeType === 1 && node.tagName.toLowerCase() === tagName) {
+            break;
+        }
+        node = node.parentNode;
+    }
+    return node;
+}
+
+function cancelSelection() {
+    if (window.getSelection) {
+        var sel = window.getSelection();
+        if (sel && !sel.isCollapsed && sel.collapseToStart) {
+            sel.collapseToStart();
+        }
+    } else if (document.selection) {
+        document.selection.clear();
+    }
+}
+
+function viewTopLeft() {
+    return {
+        left: window.scrollX || document.documentElement.scrollLeft,
+        top: window.scrollY || document.documentElement.scrollTop
+    };
+}
+
+function absNodePos(e)
+{
+    var o = {x: 0, y: 0};
+    do {
+        o.x += e.offsetLeft;
+        o.y += e.offsetTop;
+    }
+    while ((e = e.offsetParent));
+    return o;
+}
+
+// Is the mouse "near" a node?
+function mouseIsNear(node, ev) {
+    var o = absNodePos(node),
+        near = 20,
+        left = o.x - near, 
+        top = o.y - near,
+        right = o.x + node.clientWidth + near, 
+        bottom = o.y + node.clientHeight + near,
+        viewTop = window.scrollY || document.documentElement.scrollTop,
+        viewLeft = window.scrollX || document.documentElement.scrollLeft,
+        mouseX = ev.clientX + viewLeft,
+        mouseY = ev.clientY + viewTop;
+//    debug([viewLeft, ev.clientX, left, mouseX, right].join(' ') + '<br>' +
+//          [viewTop, ev.clientY, top, mouseY, bottom].join(' '));
+    return (mouseX >= left && mouseX < right &&
+            mouseY >= top && mouseY < bottom);
+}
+
+// cross platform way to fetch contained text
+function getText(node) {
+    return node.innerText || node.textContent || '';
+}
+
+/* Scroll the window so /element/ is visible.
+ * We'd rather use element.scrollIntoView, but it's not always available.
+ */
+function scrollToElement(e)
+{
+    var o = absNodePos(e);
+    window.scrollTo(o.x, o.y);
+}
+
+
+//////
+// CSS/className helpers
+
+/* return whether /element/ is a member of CSS /className/ 
+ * NOTE: does not honour the cascade
+ */
+function elementHasClass(element, className) {
+    if (!element.className) {
+        return false;
+    }
+    if (element.className === className) {
+        return true;
+    }
+    return element.className.split(' ').indexOf(className) >= 0;
+}
+
+function addClass(element, className) {
+    if (element.className) {
+        var classes = element.className.split(' ');
+        if (classes.indexOf(className) < 0) {
+            classes.unshift(className);
+            element.className = classes.join(' ');
+        }
+    } else {
+        element.className = className;
+    }
+}
+    
+function rmClass(element, className) {
+    if (!element.className) {
+        return;
+    }
+    if (element.className === className) {
+        element.className = '';
+        return;
+    }
+    var classes = element.className.split(' ');
+    var i = classes.indexOf(className);
+    if (i >= 0) {
+        classes.splice(i, 1);
+        element.className = classes.join(' ');
+    }
+}
+
+// apply a function to all elements with specified /className/
+function classApply(root, className, action)
+{
+    var condition = function (node) {
+        return elementHasClass(node, className);
+    };
+    findChild(root, condition, action);
+}
+
+
+//////
+// Cookie helpers
+
+// Create a R/O map view of the document cookies (at document load time)
+var cookiemap = null;
+function makeCookieMap() {
+    cookiemap = {};
+    var cookies = document.cookie.split(/\s*;\s*/);
+    for (var i = 0; i < cookies.length; ++i) {
+        var kv = cookies[i].split('=');
+        if (kv.length === 2) {
+            cookiemap[kv[0]] = kv[1];
+        }
+    }
+}
+makeCookieMap();
+
+// Warning: IE can ignore persistent cookies (cookies with expiry)
+function setCookie(name, value) {
+    document.cookie = name + '=' + value;
+    cookiemap[name] = value;
+}
+
+
+//////
+// More complex DOM stuff
+
+function scrollRemember(formName, pageName) {
+    var form = document.forms[formName];
+    addEvent(form, 'submit', function () {
+        var viewTop = window.scrollY || document.documentElement.scrollTop;
+        setCookie('scrollInfo', pageName + ',' + viewTop);
+        return true;
+    });
+    addEvent(window, 'load', function () {
+        if (cookiemap.scrollInfo) {
+            var fields = cookiemap.scrollInfo.split(',');
+            if (fields[0] === pageName) {
+                window.scrollTo(0, fields[1]);
+            }
+        }
+    });
+}
+
+// JS target to pop help in a new window
+function pophelp(path, target) {
+    var helpurl = path + '#' + target;
+    var height = screen.height / 2;
+    var width = screen.width / 2;
+    if (height < 400) {
+        height = 400;
+    }
+    if (width < 600) {
+        width = 600;
+    }
+    var features = "height=" + height + ",width=" + width +
+                ",resizeable=yes,scrollbars=yes,dependent=yes";
+    window.open(helpurl, "Help", features).focus();
+}
+
+function syntheticSubmit(formName, submitName) {
+    var form = document[formName];
+    if (!form) {
+        return;
+    }
+    var input = document.createElement('input');
+    input.type = 'submit';
+    input.value = 'Submit';
+    input.style.display = 'none';
+    input.name = Array.prototype.slice.call(arguments, 1).join(':');
+    form.appendChild(input);
+    input.click();
+}
+
+// JS anchor target to submit on click
+function linksubmit(formName, action)
+{
+    var submitName = Array.prototype.slice.call(arguments, 1).join(':');
+    syntheticSubmit(formName, submitName);
+}
+
+
+// Record how long a form submission takes, store result in a hidden form field
+addEvent(window, "load", function () {
+    var form;
+    for (var i = 0; i < document.forms.length; ++i) {
+        if (document.forms[i]['response_time']) {
+            form = document.forms[i];
+            break;
+        }
+    }
+    if (!form) {
+        return;
+    }
+    addEvent(form, 'submit', function () {
+        var now = new Date();
+        setCookie('submit_time', now.getTime());
+        return true;
+    });
+    var submit_time = cookiemap['submit_time'];
+    if (submit_time) {
+        setCookie('submit_time', '');
+        var now = new Date();
+        var elapsed = (now.getTime() - submit_time) / 1000;
+        if (elapsed > 0 && elapsed < 60) {
+            form['response_time'].value = elapsed;
+        }
+    }
+});
+
+
+// Allow rows in a table to be clicked through to view full record
+function clicktab(tableName, formName)
+{
+    var table = document.getElementById(tableName),
+        form = document.forms[formName];
+    if (!table || !form) {
+        return;
+    }
+    addEvent(table, 'click', function (e) {
+        var ev = e || event,
+            target = ev.target || ev.srcElement;
+        while (target && target !== table) {
+            if (target.id) {
+                syntheticSubmit(formName, target.id);
+                return false;
+            }
+            target = target.parentNode;
+        }
+        return true;
+    });
+}
+
+
+//////
+// Stuff to support the pop-up calendar
+
+function calendar_pop(elem) {
+    var date = null,
+        showtime = true,
+        params = {
+            'inputField': elem,
+            'showsOtherMonths': true
+        };
+    var calendar_select = function (cal) {
+        var p = cal.params;
+        if (p.inputField && cal.dateClicked) {
+            p.inputField.value = cal.date.print(p.dateFormat);
+        }
+        if (cal.dateClicked) {
+            cal.callCloseHandler();
+        }
+    };
+    var calendar_close = function (cal) {
+        cal.hide();
+    };
+    if (elem.disabled) {
+        return;
+    }
+    if (elem.attributes.calendarformat) {
+        params.dateFormat = elem.attributes.calendarformat.value;
+        if (params.dateFormat.indexOf('%H') < 0) {
+            showtime = false;
+        }
+    } else {
+        params.dateFormat = '%Y-%m-%d %H:%M';
+    }
+    if (elem.value) {
+        date = Date.parseDate(elem.value, params.dateFormat);
+    }
+    var cal = new Calendar(0, date, calendar_select, calendar_close);
+    cal.params = params;
+    cal.showsTime = showtime;
+    cal.setRange(1900, 2999);
+    cal.setDateFormat(cal.params.dateFormat);
+    cal.create();
+    cal.showAtElement(elem.parentNode.parentNode.parentNode);
+    cal.show();
+}
+
+var done_calendar_init = false;
+
+function calendar_init() {
+    function calendar_connect(field) {
+        if (!field.disabled) {
+            // Create a button to pop the calendar and wrap the associated 
+            // text field in divs to make space for it - gruesome.
+            var frag = document.createDocumentFragment();
+            var wrapper = document.createElement('div');
+            wrapper.style.position = 'relative';
+            if (field.size || field.style.width) {
+                wrapper.style.width = (field.clientWidth + 
+                                       2 * field.clientLeft + 'px');
+            }
+            frag.appendChild(wrapper);
+            var button_wrap = document.createElement('div');
+            button_wrap.style.width = '1.5em';
+            button_wrap.style.position = 'absolute';
+            button_wrap.style.right = '0px';
+            wrapper.appendChild(button_wrap);
+            var button = document.createElement('input');
+            button.name = field.name + 'calendar';
+            button.type = 'button';
+            button.value = '..';
+            button.style.width = '100%';
+            button_wrap.appendChild(button);
+            var field_clone = field.cloneNode(true);
+            field_clone.style.width = '100%';
+            var field_wrap = document.createElement('div');
+            field_wrap.style.marginRight = '1.5em';
+            field_wrap.style.paddingRight = '4px';
+            field_wrap.style.width = 'auto';
+            field_wrap.appendChild(field_clone);
+            wrapper.appendChild(field_wrap);
+            field.parentNode.replaceChild(frag, field);
+            addEvent(button, "click", function () {
+                calendar_pop(field_clone);
+            });
+        }
+    }
+    if (Calendar && !done_calendar_init) {
+        var i, il, cal_inputs = [];
+        for (i = 0, il = document.forms.length; i < il; ++i) {
+            var form = document.forms[i];
+            for (var j = 0, jl = form.length; j < jl; ++j) {
+                var input = form[j];
+                if (input.attributes.calendarformat) {
+                    cal_inputs.push(input);
+                }
+            }
+        }
+        for (i = 0, il = cal_inputs.length; i < il; ++i) {
+            calendar_connect(cal_inputs[i]);
+        }
+        done_calendar_init = true;
+    }
+}
+
+addEvent(window, "load", calendar_init);
+
+function enterEvent(callback) {
+    function entersub(e) {
+        var ev = e || event,
+            target = ev.target || ev.srcElement,
+            code = ev.keyCode || ev.which;
+//      if (code == 13) 
+//          alertargs(target.nodeName,target.type,inp);
+        /* ignoring select-one ENTER events is undesirable, but FF generates
+         * two of these, and we can't distinguish between them */
+        if (code === 13 && !ev.shiftKey && target.type !== 'submit' && 
+                target.type !== 'textarea' && target.type !== 'select-one') {
+            callback(target);
+            if (ev.preventDefault) {
+                ev.preventDefault();
+            } else {
+                ev.returnValue = false;
+            }
+            return false;
+        }
+    }
+    addEvent(document.body, 'keypress', entersub);
+    return entersub;
+}
+
+/* Modern browsers typically synthesize a click on the next "submit" input when
+ * the user presses <enter> in a field. This is often undesireable in complex
+ * applications. This function attempts to intercept the <enter> key event and
+ * turn it into a click on a submit button of our choosing */
+function enterSubmit(form_name, input_name) {
+    function entersub(target) {
+        var inp = document.forms[form_name][input_name];
+        if (!inp) {
+            return;
+        }
+        if (inp.length) {
+            inp = inp[0];
+        }
+        if (inp) {
+            inp.focus();
+        }
+        if (target.attributes.entersubmit) {
+            inp.click();
+        }
+    }
+    enterEvent(entersub);
+}
+
+/* When the user hits <enter> in a text field within a tbody, find the "submit"
+ * button within the body and click it */
+function enterBodySubmit(form_name) {
+    var issubmit = function (n) {
+        return (n.tagName === 'INPUT' && n.type.toLowerCase() === 'submit');
+    };
+    var form = document.forms[form_name];
+    if (form) {
+        enterEvent(function (target) {
+            var body = parentTag(target, 'tbody');
+            if (!body) {
+                return;
+            }
+            findChild(body, issubmit, function (submit) {
+                submit.click();
+                return true;
+            });
+        });
+    }
+}
+
+/* Text input showing disappearing prompt within text area */
+function inputHint(fieldId, inputPrompt)
+{
+    addEvent(window, 'load', function () {
+        var node = document.getElementById(fieldId);
+        if (!node) {
+            return;
+        }
+        var hint = document.createElement('div');
+        hint.style.position = 'absolute';
+        hint.style.overflow = 'hidden';
+        hint.style.whiteSpace = 'nowrap';
+        hint.className = 'input-hint';
+        hint.innerHTML = inputPrompt;
+        var setPosition = function () {
+            var o = absNodePos(node);
+            hint.style.left = o.x + node.clientLeft + 2 + 'px';
+            hint.style.top = o.y + 'px';
+            hint.style.lineHeight = node.offsetHeight + 'px';
+        };
+        setPosition();
+        node.parentNode.appendChild(hint);
+        var state = {focus: false, mouse: false};
+        var refresh = function () {
+            if (state.mouse || state.focus || node.value) {
+                hint.style.display = 'none';
+            } else {
+                hint.style.display = 'block';
+            }
+        };
+        refresh();
+        addEvent(hint, 'mouseover', function () {
+            state.mouse = true;
+            refresh();
+        });
+        addEvent(node, 'mouseout', function () {
+            state.mouse = false;
+            refresh();
+        });
+        addEvent(node, 'focus', function () {
+            state.focus = true;
+            refresh();
+        });
+        addEvent(node, 'blur', function () {
+            state.focus = false;
+            refresh();
+        });
+        addEvent(window, 'resize', setPosition);
+        enterEvent(function () {
+            node.form.submit();
+        });
+    });
+}
+
+function radio_skip(srcname, srcvalue, dstname) {
+    var dsts = document.getElementsByName(dstname);
+    var srcs = document.getElementsByName(srcname);
+    var check = function (ev) {
+        var src = ev.target ? ev.target : ev.srcElement;
+        var disable = src.value === srcvalue;
+        for (var i = 0; i < dsts.length; ++i) {
+            dsts[i].disabled = disable;
+        }
+    };
+    for (var i = 0; i < srcs.length; ++i) {
+        var src = srcs[i];
+        addEvent(src, "change", check);
+        if (src.checked && src.value === srcvalue) {
+            for (var j = 0; j < dsts.length; ++j) {
+                dsts[j].disabled = true;
+            }
+        }
+    }
+}
+
+function linkfold(name, initially_closed) {
+    var label = document.getElementById('label_' + name);
+    var node = document.getElementById('fold_' + name);
+    var icon = document.getElementById('icon_' + name);
+    if (!label || !node) {
+        return;
+    }
+
+    var fold_state = function () {
+        var folds = cookiemap['folds'] ? cookiemap['folds'].split(',') : [];
+        for (var i = 0; i < folds.length; i++) {
+            var state = folds[i];
+            if (state.substr(0, state.length - 1) === name) {
+                return (state.charAt(state.length - 1) === '+');
+            }
+        }
+        return initially_closed;
+    };
+
+    var fold_set_state = function (want_state) {
+        var folds = cookiemap['folds'] ? cookiemap['folds'].split(',') : [];
+        for (var i = 0; i < folds.length; i++) {
+            var state = folds[i];
+            if (state.substr(0, state.length - 1) === name) {
+                if (want_state !== (state.charAt(state.length - 1) === '+')) {
+                    folds[i] = name + (want_state ? '+' : '-');
+                    setCookie('folds', folds.join(','));
+                }
+                return;
+            }
+        }
+        folds.unshift(name + (want_state ? '+' : '-'));
+        setCookie('folds', folds.join(','));
+    };
+
+    var fold_open = function () {
+        node.style.display = '';
+        if (icon) {
+            icon.innerHTML = '-';
+        }
+        fold_set_state(false);
+    };
+
+    var fold_close = function () {
+        node.style.display = 'none';
+        if (icon) {
+            icon.innerHTML = '+';
+        }
+        fold_set_state(true);
+    };
+    var fold_toggle = function () {
+        if (node.style.display === 'none') {
+            fold_open();
+        } else {
+            fold_close();
+        }
+    };
+    if (icon) {
+        icon.style.display = 'block';
+        icon.innerHTML = '-';
+        icon.style.cursor = 'pointer';
+        if (!isChild(label, icon)) {
+            addEvent(icon, 'click', fold_toggle);
+        }
+    }
+    addEvent(label, 'click', fold_toggle);
+    label.style.cursor = 'pointer';
+    if (cookiemap === null) {
+        makeCookieMap();
+    }
+    if (fold_state()) {
+        fold_close(node, icon);
+    }
+}
+
+function droplist(name) {
+    var label = document.getElementById('label_' + name);
+    var list = document.getElementById('list_' + name);
+    var icon = document.getElementById('icon_' + name);
+
+    var showList = function () {
+        list.style.display = 'block';
+        list.style.left = '0px'; 
+        list.style.top = label.offsetHeight + 'px';
+        label.style.zIndex = 3000; // IE: makes list.style.zIndex work
+        list.style.zIndex = 1000;
+        list.setAttribute('tabIndex', -1);
+        list.focus();
+    };
+    var hideList = function () {
+        list.style.display = 'none';
+        label.style.zIndex = 0;
+        list.style.zIndex = 0;
+    };
+    var listClick = function (e) {
+        var ev = e || event,
+            target = ev.target || ev.srcElement;
+        if (target.id) {
+            syntheticSubmit('appform', target.id);
+        }
+    };
+    icon.setAttribute('tabIndex', 0);
+    addEvent(icon, 'mousedown', showList);
+    addEvent(icon, 'focus', showList);
+    addEvent(list, 'blur', hideList);
+    addEvent(list, 'click', listClick);
+    icon.style.cursor = 'pointer';
+    list.style.position = 'absolute';
+}
+
+
+if (isOldIE) {
+    addEvent(window, 'load', function () {
+        // Expensive workaround for IE's limited :hover support
+        var fixNode = function (node) {
+            var orig_bg = node.currentStyle.backgroundColor;
+            addEvent(node, 'mouseover', function () {
+                node.style.backgroundColor = '#fffae7';
+            });
+            addEvent(node, 'mouseout', function () {
+                node.style.backgroundColor = orig_bg;
+            });
+            node.style.cursor = 'pointer';
+        };
+        var walkNode = function (node) {
+            var nrows, rownum, row, cell, i, l;
+            if (node.className.search('clicktab') >= 0) {
+                nrows = node.rows.length < 1000 ? node.rows.length : 1000;
+                for (rownum = 0; rownum < nrows; ++rownum) {
+                    row = node.rows[rownum];
+                    if (row.id) {
+                        fixNode(row);
+                    } else {
+                        for (i = 0, l = row.cells.length; i < l; ++i) {
+                            cell = row.cells[i];
+                            if (cell.id) {
+                                fixNode(cell);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if (node.className.search('clickable') >= 0) {
+                fixNode(node);
+            }
+            if (node.children.length) {
+                for (i = 0, l = node.children.length; i < l; ++i) {
+                    walkNode(node.children[i]);
+                }
+            }
+        };
+        walkNode(document.body);
+    });
+}
+/*jslint white: true, browser: true, devel: true, sub: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..0422347
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+ <head>
+  <meta http-equiv="refresh" content="1;/cgi-bin/{{APPNAME}}/app.py">
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/app/lang/calendar-en.js b/app/lang/calendar-en.js
new file mode 100644
index 0000000..0dbde79
--- /dev/null
+++ b/app/lang/calendar-en.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mihai_bazon at yahoo.com>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible.  We strongly believe that
+// Unicode is the answer to a real internationalized world.  Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary.  We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+//   Calendar._SDN_len = N; // short day name length
+//   Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL.  See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent.  It specifies the week-end days, as an array
+// of comma-separated numbers.  The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/app/menu.py b/app/menu.py
new file mode 100644
index 0000000..e2a3a37
--- /dev/null
+++ b/app/menu.py
@@ -0,0 +1,161 @@
+#!/usr/local/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import re
+import albatross
+import config
+try:
+    from albatross import fcgiappnew as fcgiapp
+except ImportError:
+    from albatross import fcgiapp
+
+template = r'''
+<al-include name="page_layout.html" />
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">NetEpi Collection Application</al-setarg>
+  <script language="JavaScript">
+    function newWindow(fileName,windowName) {
+      var flags = [
+        'height=' + screen.availHeight,
+        'width=' + screen.availWidth,
+        'toolbar=no',
+        'scrollbars=yes',
+        'status=yes',
+        'menubar=no',
+        'hotkeys=no',
+        'location=no',
+        'resizable=yes',
+        'copyhistory=no'
+      ];
+      var w = window.open(fileName, windowName, flags.join()); 
+      if (w) {
+        w.focus();
+        w.moveTo(0,0);
+      }
+      return !w;
+    }
+  </script>
+  <ul>
+  <li><al-value expr="jslink('app', 'User application')" noescape /></li>
+  </ul>
+  <al-if expr="addrs">
+   This NetEpi Collection server should be remotely accessible as <al-value expr="' or '.join(addrs)" />.
+  <al-else>
+   This NetEpi Collection server does not appear to have a public IP
+   address although the applications will still be accessible from
+   this machine.
+  </al-if>
+  <al-if expr="vids">
+   <p>
+   Demonstration videos:<br>
+   <ul>
+    <al-for iter="v" expr="vids">
+     <al-exec expr="u, l = v.value()" />
+     <li><al-a expr="u"><al-value expr="l" /></al-a></li>
+    </al-for>
+   </ul>
+   </p>
+  </al-if>
+</al-expand>
+'''
+
+ip_re = re.compile('\s+inet addr:(\S+)')
+flags_re = re.compile('\s+(.*)\s+MTU:')
+
+def get_addrs():
+    iface = addr = up = None
+    for line in os.popen('/sbin/ifconfig'):
+        if not line[0].isspace():
+            if iface and up and addr:
+                yield 'http://%s/%s/' % (addr, config.appname)
+            iface, rest = line.split(None, 1)
+            addr = up = None
+        else:
+            match = ip_re.match(line)
+            if match:
+                addr = match.group(1)
+            else:
+                match = flags_re.match(line)
+                if match:
+                    flags = match.group(1).split()
+                    up = 'UP' in flags and 'LOOPBACK' not in flags
+
+def get_videos():
+    ext = '.html'
+    try:
+        filenames = os.listdir(os.path.join(config.html_target, 'video'))
+    except OSError:
+        pass
+    else:
+        for fn in filenames:
+            if fn.endswith(ext):
+                label = fn[:-len(ext)].replace('_', ' ')
+                yield os.path.join('/', config.appname, 'video', fn), label
+
+class Context(albatross.SimpleAppContext):
+    def __init__(self, app):
+        albatross.SimpleAppContext.__init__(self, app)
+        self.locals.appath = self.appath
+        self.locals.appname = config.appname
+        self.locals.apptitle = config.apptitle
+        self.locals._credentials = None
+        self.locals.debug = False
+        self.locals.get_messages = self.get_messages
+        self.locals.get_errors = self.get_messages
+        self.locals.has_js = False
+        self.locals.request_start = 0
+        self.locals.request_elapsed = self.request_elapsed
+        self.locals.session_timeout = None
+        self.locals.__page__ = 'menu'
+        self.locals.jslink = self.jslink
+        self.locals.addrs = list(get_addrs())
+        self.locals.vids = list(get_videos())
+
+    def jslink(self, app, label):
+        return ('<a href="/cgi-bin/%s/%s.py"\n'
+                '  onclick="return newWindow(\'/cgi-bin/%s/%s.py\',\'%s\')">'
+                '%s</a>' % (config.appname, app,
+                            config.appname, app, app,
+                            label))
+                                        
+    def appath(self, *args):
+        return '/'.join(('', self.locals.appname) + args)
+
+    def get_messages(self):
+        return []
+
+    def request_elapsed(self):
+        return 0
+
+    def redirect_url(self, href):
+        return href
+
+    def current_url(self):
+        return ''
+
+app = albatross.SimpleApp('.', 'pages', None, '<secret>')
+while fcgiapp.running():
+    req = fcgiapp.Request()
+    ctx = Context(app)
+    ctx.set_request(req)
+    tmpl = albatross.Template(ctx, '<magic>', template)
+    tmpl.to_html(ctx)
+    ctx.flush_content()
+    req.return_code()
diff --git a/app/nobbleback.js b/app/nobbleback.js
new file mode 100644
index 0000000..e24b23b
--- /dev/null
+++ b/app/nobbleback.js
@@ -0,0 +1,87 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+_nbb_debug = false;
+
+/* Nobble Back Button via a hidden iframe */
+function _nbb_clickBack(win) {
+//    alert('intercepted back click ' + win.document.title);
+    if (_nbb_debug) alert('synth << Back click');
+    for (var i = 0; i < win.document.forms.length; ++i) {
+        var back = win.document.forms[i]['back'];
+        if (back) {
+            if (back.length)
+                back = back[0];
+            back.click();
+            return true;
+        }
+    }
+    return false;
+}
+
+function _nbb_frame_redir (frame) {
+//    alert('_nbb_frame_redir frame ' + frame);
+    frame._nbb_redir = function () {
+        var t = (new Date()).getTime();
+        //var e = window.parent.backiFrame;
+        //e.src = e.src + '?t=' + t;
+        frame.location = frame.location + '?t=' + t;
+        if (_nbb_debug) alert('frame redir ' + frame.parent._nbb_window_loaded);
+    }
+    // Firefox doesn't update history if redirect is done immediately
+    frame.setTimeout("_nbb_redir()", 1);
+}
+
+function _nbb_frame_load (frame) {
+    var win = frame.parent;
+    var loc = frame.location;
+    var query = loc.search;
+    if (!frame || !win)
+        alert('oops ' + frame + win);
+    if (query.charAt(0) != '?') {
+        win._nbb_frame_loaded = true;
+        if (win._nbb_frame_redirected) {
+            if (_nbb_debug) alert('frame load (back)');
+            if (!_nbb_clickBack(win))
+                _nbb_frame_redir(frame);
+        } else if (win._nbb_window_loaded) {
+            if (_nbb_debug) alert('frame load');
+            _nbb_frame_redir(frame);
+        }
+    } else {
+        if (_nbb_debug) alert('frame load (setting redirected)');
+        win._nbb_frame_redirected = true;
+    }
+}
+
+function _nbb_window_load() {
+    if (_nbb_debug) alert('window load ' + window._nbb_window_loaded);
+    window._nbb_window_loaded = true;
+    window.backiFrame = document.getElementById("bif");
+    if (!window.backiFrame)
+        alert('window.backiFrame is null');
+    if (window._nbb_frame_loaded)
+        _nbb_frame_redir(frames["bif"]);
+}
+
+_nbb_frame_redirected = false;
+_nbb_window_loaded = false;
+_nbb_frame_loaded = false;
+if (window.addEventListener)
+    window.addEventListener("load", _nbb_window_load, false);
+else
+    window.attachEvent("onload", _nbb_window_load);
diff --git a/app/printforms.css b/app/printforms.css
new file mode 100644
index 0000000..a22f4d1
--- /dev/null
+++ b/app/printforms.css
@@ -0,0 +1,192 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+body {
+    background-color: white;
+    color: black;
+    font-family: "Verdana", "Arial", sans-serif;
+}
+
+ at media print {
+    .noprint {
+        display: none;
+    }
+    body {
+        font-size: 8pt;
+    }
+}
+.syndform {
+    page-break-after: always;
+    width: 100%;
+}
+.cpsection {
+    width: 100%;
+}
+    
+ at media screen {
+    body {
+        font-size: 10pt;
+    }
+    .syndform {
+        border-bottom: 2px dashed black;
+    }
+    .cpsection {
+        border-bottom: 2px dashed black;
+    }
+    .confidential {
+        display: none;
+    }
+}
+.msgbox {
+    color: #080;
+    background-color: #efe;
+    border-left: 1ex solid #080;
+    padding-left: 1ex;
+    margin-top: 0.5ex;
+}
+
+.errbox {
+    color: #c00;
+    background-color: #fee;
+    border: 1px solid #c00;
+    border-left: 1ex solid #c00;
+    padding-left: 1ex;
+    margin-top: 0.5ex;
+    font-weight: bold;
+}
+
+.confidential {
+    text-align: center;
+    font-weight: bold;
+}
+
+.butt {
+    width: 6em;
+}
+td {
+    vertical-align: top;
+}
+
+.heading {
+    font-size: 120%;
+    font-weight: bold;
+    padding-top: 3ex;
+    padding-bottom: 1ex;
+    text-align: center;
+    text-decoration: underline;
+}
+
+.section {
+    font-size: 110%;
+    font-weight: bold;
+    padding-top: 2ex;
+    padding-bottom: 1ex;
+}
+.subsection {
+    font-size: 110%;
+    font-weight: bold;
+    padding-top: 1.5ex;
+    padding-bottom: 1ex;
+}
+.number {
+    width: 8ex;
+}
+.question, .inputs {
+    padding-top: 1ex;
+    padding-bottom: 0.5ex;
+    page-break-inside: avoid;
+}
+.info {
+    font-size: 83%;
+}
+.inputs {
+    width: 35%;
+}
+.skiptext {
+    font-size: 83%;
+    color: #888;
+    font-weight: bold;
+}
+.pretext, .posttext {
+    font-size: 83%;
+}
+.posttext {
+    padding-bottom: 0.5ex;
+}
+.pretext {
+    padding-top: 0.5ex;
+}
+.inputs .textbox {
+    height: 1.5em;
+    width: 99%;
+    border-bottom: 1px solid black;
+    margin: 1px;
+}
+.inputs .textlines {
+    height: 1.5em;
+    width: 100%;
+    border-bottom: 1px dotted black;
+    margin-bottom: 1px;
+}
+.inputs .tickbox {
+    height: 1em;
+    width: 1em;
+    border: solid 1px black;
+    margin-right: 1ex;
+}
+.linereport table {
+    border-collapse: collapse;
+}
+.linereport thead {
+    display: table-header-group;
+}
+.linereport tbody tr:hover {
+    background-color: #fffae7;
+}
+.linereport .header {
+    text-align: center;
+    font-weight: bold;
+    font-size: 120%;
+}
+.linereport .preamble {
+    font-size: 83%;
+}
+.linereport .footer {
+    font-size: 83%;
+}
+.linereport th {
+    text-align: left;
+    background-color: #ccc;
+    border-top: 2px solid black;
+    padding: 0 2px 0 2px;
+}
+.linereport .lighter {
+    background-color: #eee;
+    border-top: 1px solid black;
+}
+.linereport .darker {
+    background-color: #ddd;
+}
+.linereport .lighter td, .linereport .darker td {
+    border-top: 1px solid black;
+    padding: 0 2px 0 2px;
+}
+.linereport .free {
+    padding-left: 4ex;
+    color: #888;
+}
diff --git a/app/sorttable.js b/app/sorttable.js
new file mode 100644
index 0000000..6e47da6
--- /dev/null
+++ b/app/sorttable.js
@@ -0,0 +1,119 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+/* Sort a table that has been prepared by sorttable_init using the column
+ * specified by /index/
+ */
+
+function sorttable (context, index) {
+    function order (a, b) {
+        if (isNaN(a.num) || isNaN(b.num)) {
+            return a.text < b.text ? -context.direction : 
+                        (a.text > b.text ? context.direction : 0);
+        } else
+            return (a.num - b.num) * context.direction;
+    }
+    if (context.lastIndex == index)
+        context.direction = -context.direction;
+    context.lastIndex = index;
+    /* Create a new tBody list, sort it, and reattach */
+    for (var tbi = 0; tbi < context.table.tBodies.length; ++tbi) {
+        var tbody = context.table.tBodies[tbi];
+        var rows = new Array();
+        var classNames = new Array();
+        for (var i = 0; i < tbody.rows.length; ++i) {
+            var row = tbody.rows[i];
+            if (row.cells.length != context.ncols) continue;
+            var text = getText(row.cells[index]).toLowerCase();
+            rows[i] = {
+                row: row, 
+                text: text,
+                num: Number(text)
+            }
+            classNames[i] = row.className;
+        }
+        if (!rows.length) continue;
+        rows.sort(order);
+        for (var ri = 0; ri < rows.length; ++ri) {
+            var row = rows[ri].row;
+            row.className = classNames[ri];
+            tbody.appendChild(row);
+        }
+    }
+    /* Update sort column and direction indicating arrows */
+    for (var thi=0, ll=context.table.tHead.rows.length; thi < ll; ++thi) {
+        var row = context.table.tHead.rows[thi];
+        for (var i = 0; i < row.cells.length; ++i) {
+            var header = row.cells[i];
+            if (elementHasClass(header, 'sortable')) {
+                var arrows = header.firstChild;
+                if (index == i) {
+                    if (context.direction < 0)
+                        arrows.innerHTML = '▲';
+                    else
+                        arrows.innerHTML = '▼';
+                }
+                else
+                    arrows.innerHTML = '';
+            }
+        }
+    }
+}
+
+/* finds all the tables of class /sorttable/ in the document, and makes any
+ * <thead> cells that are class /sortable/ clickable. When clicked, the table
+ * is sorted by that column. The table must be regular with no spans or missing
+ * cells.
+ */
+function sorttable_init () {
+    function get_click_closure (context, i) { 
+        return function (event) { return sorttable(context, i); }
+    }
+    var context;
+    var defaultsort;
+    var tables = document.getElementsByTagName("table");
+    for (var i = 0; i < tables.length; ++i) {
+        if (elementHasClass(tables[i], 'sorttable')) {
+            context = {lastIndex: -1, direction: 1, table: tables[i]}
+            defaultsort = undefined;
+            for (var thi = 0; thi < context.table.tHead.rows.length; ++thi) {
+                var row = context.table.tHead.rows[thi];
+                context.ncols = row.cells.length;
+                for (var i = 0; i < row.cells.length; ++i) {
+                    var header = row.cells[i];
+                    if (elementHasClass(header, 'sortable')) {
+                        var click = get_click_closure(context, i);
+                        addEvent(header, "click", click);
+                        var div = document.createElement('div');
+                        div.style.width = '1.4em';
+                        div.style.fontSize = '90%';
+                        div.style.textAlign = 'center';
+                        div.style.cssFloat = div.style.styleFloat = 'right';
+                        header.insertBefore(div, header.firstChild);
+                        if (elementHasClass(header, 'defaultsort'))
+                            defaultsort = i;
+                    }
+                }
+            }
+            if (defaultsort != undefined)
+                sorttable(context, defaultsort);
+        }
+    }
+}
+addEvent(window, "load", sorttable_init);
+
diff --git a/app/style-gallery.html b/app/style-gallery.html
new file mode 100644
index 0000000..4b32d1a
--- /dev/null
+++ b/app/style-gallery.html
@@ -0,0 +1,1091 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta http-equiv="Content-Script-Type" content="text/javascript">
+  <link rel="shortcut icon" hrefexpr="/images/favicon.ico" type="image/x-icon">
+  <style type="text/css" media="all">
+   @import "/collection/style.css";
+   body { font-size: 9pt; }
+   .sg {
+    clear: both;
+    border-left: 2em solid #bbb;
+    border-right: 2em solid #bbb;
+    border-bottom: 2em solid #bbb;
+   }
+   .sg .sgsg {
+    background: #bbb;
+    clear: both;
+    margin-left: -2em;
+    padding-left: 2em;
+    margin-bottom: 0;
+   }
+  </style>
+  <title>NetEpi Collection - stylesheet gallery</title>
+ <body>
+  <h1>NetEpi Collection - stylesheet gallery</h1>
+  <p>This is mainly for developers, but also serves as a sort of
+  regression test for the main application style sheet. At this stage,
+  it is incomplete. Also note, all paths are hard-coded to expect the
+  application resources to be installed at /collection/.</p>
+
+  <h2>common stuff</h2>
+
+   <div class="err">err</div>
+   <div class="reverr">reverr <input type="submit"></div>
+   <div class="boxed">boxed</div>
+   <div class="deleted" style="height: 10ex;">deleted</div>
+   <div class="required">required (should be red)</div>
+   <div class="preview">preview (should be inset)</div>
+   <div class="danger">danger</div>
+   <div class="smaller">smaller</div>
+   <a href="#">a link</a>
+   <div class="content">content (indent by 2ex)</div>
+   <h2 class="pagetitle">pagetitle</h2>
+   <div class="pagesubtitle">pagesubtitle</div>
+   <div class="copyright">copyright</div>
+   <div>(fauxrule follows)</div>
+   <div class="fauxrule"></div>
+   <div class="more">more (float right)</div>
+   <div class="debug">debug (box)</div>
+   <input class="smallbutt" type="submit" value="smallbutt">
+   <input class="butt" type="submit" value="butt">
+   <input class="bigbutt" type="submit" value="bigbutt">
+   <div class="centbox"><h1>centbox</h1>centbox content</div>
+   <div class="userdetails">userdetails (italic, smaller)</div>
+   <div class="msgbox">msgbox</div>
+   <div class="errbox">errbox</div>
+   <div class="adminbox">adminbox</div>
+
+  <div class="sg">
+   <h2 class="sgsg">pagebanner</h2>
+   <table width="100%" border="0" class="pagebanner">
+    <tr>
+     <td><img alt="NetEpi logo" src="/collection/images/netepi.png" /></td>
+     <th>NetEpi Collection</th>
+     <td>|</td>
+     <td><a id="Admin" href="#">Admin</a></td>
+     <td>|</td>
+     <td><a id="Tasks" href="#">Tasks</a></td>
+     <td>|</td>
+     <td><a id="Search" href="#">Search</a></td>
+     <td>|</td>
+     <td><a id="Tools" href="#">Tools</a></td>
+     <td>|</td>
+     <td><button id="help"><img border="0" alt="Help" src="/collection/images/help.png" /></button></td>
+     <td>|</td>
+     <td><a id="Logout" href="#">Logout</a></td>
+    </tr>
+   </table>
+  </div>
+
+
+  <div class="sg">
+   <h2 class="sgsg">syndlist</h2>
+   <div class="syndlist">
+    <h2 class="pagetitle">Case/Contact Definition</h2>
+    <table width="100%" cellspacing="0" border="0">
+     <tbody class="case">
+      <tr class="header">
+       <td class="title left-blob" width="100%" colspan="2">SARS</td>
+       <td class="info"><input type="image" align="absmiddle" src="/collection/images/info.png" name="syn_detail:1" /></td>
+       <td class="count right-line" nowrap align="right">54 records</td>
+      </tr>
+      <tr>
+       <td class="desc left-line right-line" colspan="4"> 
+        Severe Acute Respiratory Syndrome</p></td>
+      </tr>
+      <tr class="footer">
+       <td class="date left-line" nowrap>Posted 15/05/2006 18:16:00</td>
+       <td align="right" class="buttons">
+        <input type="submit" class="bigbutt" name="new:1" value="Add Case" /><input type="submit" class="bigbutt" name="edit:1" value="Edit Case" /></td>
+       <td align="right" nowrap colspan="2" class="extra right-blob">
+        <select onchange="submit();" name="syndextra">
+         <option value="">more options</option>
+         <option value="reports:1">Reports</option>
+         <option value="printforms:1">Print blank forms</option>
+         <option value="export:1">Export records</option>
+         <option value="import:1">Import data</option>
+         <option value="contactvis:1">Visualise contacts</option>
+         </select>
+        <input type="submit" class="smallbutt" name="go" value="go" />
+       </td>
+      </tr>
+      <tr><td colspan="4" class="line"></td></tr>
+     </tbody>
+     <tbody class="contact">
+      <tr class="header">
+       <td class="title left-blob" width="100%" colspan="2">SARS followup</td>
+       <td class="info"></td>
+       <td class="count right-line" nowrap align="right">26 records</td>
+      </tr>
+      <tr>
+       <td class="desc left-line right-line" colspan="4">Sars Followup</p></td>
+      </tr>
+      <tr class="footer">
+       <td class="date left-line" nowrap>Posted 17/05/2006 12:08:00</td>
+       <td align="right" class="buttons">
+        <input type="submit" class="bigbutt" name="new:3" value="Add Contact" />
+        <input type="submit" class="bigbutt" name="edit:3" value="Edit Contact" />
+       </td>
+       <td align="right" nowrap colspan="2" class="extra right-blob">
+        <select onchange="submit();" name="syndextra">
+         <option value="">more options</option>
+         <option value="reports:3">Reports</option>
+         <option value="printforms:3">Print blank forms</option>
+         <option value="export:3">Export records</option>
+         <option value="import:3">Import data</option>
+         <option value="contactvis:3">Visualise contacts</option>
+        </select>
+        <input type="submit" class="smallbutt" name="go" value="go" />
+       </td>
+      </tr>
+      <tr>
+       <td colspan="4" class="line"></td>
+      </tr>
+     </tbody>
+    </table>
+   </div>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">labelform</h2>
+   <table>
+    <tbody class="labelform details">
+     <tr>
+      <td><label for="search.syndrome_id">Case/Contact Definition</label></td>
+      <td>
+       <select id="search.syndrome_id" name="search.syndrome_id">
+        <option value="Any">Any</option>
+        <option value="1">SARS</option>
+        <option value="3">SARS followup</option>
+       </select>
+      </td>
+      <td><label for="search.local_case_id">Local ID</label></td>
+      <td><input id="search.local_case_id" name="search.local_case_id" value="" /></td>
+     </tr>
+     <tr>
+      <td><label for="search.deleted">Deleted</label></td>
+      <td>
+       <input type="radio" class="autowidth" id="search.deleted" name="search.deleted" value="n" checked /> No  
+       <input type="radio" class="autowidth" id="search.deleted" name="search.deleted" value="y" /> Yes  
+       <input type="radio" class="autowidth" id="search.deleted" name="search.deleted" value="" /> Both  
+      </td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+
+  <div class="sg">
+   <h2 class="sgsg">widelabelform</h2>
+   <table>
+    <tbody class="widelabelform">
+     <tr>
+      <th colspan="3">Searching</th>
+     </tr>
+     <tr>
+      <td class="label">
+       <label for="preferphonetic">Prefer phonetic</label></td>
+      <td id="preferphonetic" class="autowidth">
+       <input type="radio" name="phonetic_search" value="True" checked /> Yes
+       <input type="radio" name="phonetic_search" value="False" /> No
+      </td>
+      <td><input type="submit" class="butt" name="reset:phonetic_search" value="Reset" /></td>
+     </tr>
+     <tr>
+      <td class="label">
+       <label for="resultsperpage">Results per page</label></td>
+      <td id="resultsperpage" class="autowidth">
+       <input type="radio" name="results_per_page" value="10" checked /> 10
+       <input type="radio" name="results_per_page" value="25" /> 25
+       <input type="radio" name="results_per_page" value="50" /> 50
+      </td>
+      <td><input type="submit" class="butt" name="reset:results_per_page" value="Reset" /></td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">key</h2>
+   <div class="key">
+    <b>Choosing a good password</b>
+    <p>
+    You must choose a password which is known only to you and which is
+    hard to guess. It must be at least 6 characters long and must contain a
+    mixture of letters and numbers. The letters must be a mixture of upper
+    and lower case. It is unwise to base your password on obvious words or
+    sets of numbers, such as your name, or your partner's or employer's
+    name, or your birthday, telephone number, car registration number,
+    and so on.
+    </p>
+    <p><span class="required">*</span> - required field.</p>
+    <p><span class="onerequired">*</span> - at least one field must be supplied.</p>
+   </div>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">bull-summ</h2>
+   <div class="bull-summ">
+    <input type="hidden" name="bulletin_detail" value="" />
+    <div class="banner">
+     <div class="hide"><a href="#">Hide</a></div>
+     News Bulletins
+    </div>
+    <div class="item">
+     <div class="header">item header</div>
+     <div class="synopsis">item synopsis</div>
+     <div class="date">
+      <div class="more"><a href="#">More»</a></div>
+      Posted 11/01/2008</div>
+    </div>
+   </div>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">bulletin-detail</h2>
+   <div class="bulletin-detail">
+    <table width="100%" border="0" />
+     <tr>
+      <th>Title:</th>
+      <td>Blah blah!</td>
+     </tr>
+     <tr>
+      <th>Posted:</th>
+      <td>21/08/2006 17:46:11</td>
+     </tr>
+     <tr>
+      <th></th>
+      <td>this is the detail text</td>
+     </tr>
+    </table>
+    <div class="back"><input type="submit" name="back" value="Back" /></div>
+   </div>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">login</h2>
+   <table class="login" cellpadding="0" cellspacing="0" border="0">
+    <tr>
+     <td class="lhs logo">
+      <img alt="NetEpi Collection logo" src="/collection/images/netepi-bb.png" /></td>
+     <td class="rhs">
+      <div class="title">
+       NetEpi Collection</div>
+      <div class="subtitle">
+       Network-enabled tools for epidemiology and public health practice.</div>
+      <div class="copyright">
+       <a target="_blank" href="/collection/help/copyright.html">Copyright ©</a> 2004-2010 NSW Department of Health and others.<br>
+       <a href="http://www.netepi.org/">NetEpi Collection</a> version 9.9.9 12345<br>
+      </div>
+     </td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs">
+      <label for="username">User name</label>
+     </td>
+     <td class="rhs">
+      <input type="text" id="username" tabindex="1" name="username" /></td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs">
+      <label for="password">Password</label>
+     </td>
+     <td class="rhs">
+      <input type="password" id="password" tabindex="2" name="password" value="" /></td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs"> </td>
+     <td class="rhs">
+      <input type="submit" tabindex="4" name="login" value="Log in" /></td>
+    </tr>
+    <tr class="apply prompt">
+     <td class="lhs">
+      <a class="help" target="_blank" href="/collection/help/help.html#login"><img border="0" alt="Help" src="/collection/images/help-w.png" /></a></td>
+     <td class="rhs">
+       <input type="submit" name="register" value="Apply for a user account" /></td>
+    </tr>
+    <tr class="dedication">
+     <td class="lhs"> </td>
+     <td class="rhs">
+      Dedicated to the memory of Professor Aileen Plant, 1948-2007.<br>
+      <a href="http://memorial.curtin.edu.au/aileenplant/">[1]</a> <a href="http://www.smh.com.au/news/obituaries/she-fought-disease-despite-risks/2007/04/15/1176575678645.html?page=fullpage">[2]</a> <a href="http://en.wikipedia.org/wiki/Aileen_Plant">[3]</a> <a href="http://www.publish.csiro.au/view/journals/dsp_journal_fulltext.cfm?nid=226&f=NB07067">[4]</a> <a href="http://www.who.int/mediacentre/news/statements/2007/s07/en/index.html">[5]</a> <a href="http://www.mja.com.au/public [...]
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">formslist</h2>
+   <table cellspacing="0" class="formslist" cellpadding="1">
+    <tr>
+     <th colspan="2" class="formname">Exposure History (SARS)</th>
+     <th align="center" width="5%"></th>
+    </tr>
+    <tr>
+     <td class="formid">
+      F490<br>
+      25/08/2006<br>
+      14:23:57</td>
+     <td width="100%">
+      Contact with case: No; Contact duration (hours): 123.0; Date of
+      first contact: 13/08/2007; Most recent contact: 16/08/2007</td>
+     <td align="center">
+      <input type="submit" class="butt" name="edit:sars_exposure:49" value="Edit" /></td>
+    </tr>
+    <tr>
+     <th colspan="2" class="formname">Travel history - China (SARS)</th>
+     <th align="center" width="5%">
+      <input type="submit" class="butt" name="new:sars_china" value="New" /></th>
+    </tr>
+    <tr>
+     <td class="formid">
+      F580<br>
+      02/07/2007<br>
+      13:46:13</td>
+     <td width="100%">
+      Transit airport: n/a; Transit date: n/a; Transit period: n/a;
+      Left airport: Not answered; Arrived China:: 30/01/2007; Departed
+      China:: 30/01/2007; Visited Guangdong: No; Visited Shanxi: No;
+      Visited Beijing: No; Other provinces:: n/a; Visited health care
+      fac:: No; Reason: n/a</td>
+     <td align="center">
+      <input type="submit" class="butt" name="edit:sars_china:58" value="Edit" /></td>
+    </tr>
+    <tr>
+     <td class="formid">
+      F720<br>
+      04/09/2007<br>
+      10:25:09</td>
+     <td width="100%">
+      Transit airport: n/a; Transit date: 01/09/2007; Transit period: n/a;
+      Left airport: Not answered; Arrived China:: 02/09/2007; Departed
+      China:: 03/09/2007; Visited Guangdong: Not answered; Visited Shanxi:
+      Not answered; Visited Beijing: Not answered; Other provinces::
+      n/a; Visited health care fac:: Not answered; Reason: n/a</td>
+     <td align="center">
+      <input type="submit" class="butt" name="edit:sars_china:72" value="Edit" /></td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">syndrome-form</h2>
+   todo
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">selexp</h2>
+   <table border="0" class="selexp">
+    <tr>
+     <td width="30%">Case or contact forms?</td>
+     <td>
+      <table>
+       <tr>
+        <td><input type="radio" name="forms.contact_syndrome_id" value="" checked /></td>
+        <td>Cases</td>
+       </tr>
+       <tr>
+        <td><input type="radio" name="forms.contact_syndrome_id" value="3" /></td>
+        <td>Sars Followup</td>
+       </tr>
+      </table>
+     </td>
+     <td align="center"><input type="submit" class="butt" name="set" value="Set" /></td>
+    </tr>
+    <tr>
+     <td width="30%">Which forms would you like to print?</td>
+     <td>
+      <table>
+       <tr>
+        <td><input type="checkbox" name="forms.include_forms" value="_demographics" /></td>
+        <td width="100%">Demographics/Identification</td>
+       </tr>
+       <tr>
+        <td><input type="checkbox" name="forms.include_forms" value="sars_exposure" /></td>
+        <td width="100%">Exposure History (SARS)</td>
+       </tr>
+       <tr>
+        <td><input type="checkbox" name="forms.include_forms" value="sars_china" /></td>
+        <td width="100%">Travel history - China (SARS)</td>
+       </tr>
+      </table>
+     </td>
+     <td align="center">
+      <input type="submit" class="bigbutt" name="select_all" value="Select All" /><br />
+      <input type="submit" class="bigbutt" name="clear_all" value="Clear Selection" /></td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">pt-search</h2>
+   <table>
+    <tr>
+     <td width="50%" valign="top" class="pt-search">
+      <table width="100%" border="0" cellspacing="0">
+       <tr>
+        <th colspan="3" align="left">Case/Contact Definition Forms</th>
+       </tr>
+       <tr>
+        <td width="14"><img height="10" width="14" alt="" src="/collection/images/button-nil.png" /></td>
+        <td width="14"><input type="image" height="10" width="14" alt="dn" src="/collection/images/button-down.png" name="pt_search:move_dn:0" /></td>
+        <td width="90%" class="lighter">sars_exposure</td>
+        <td width="5%"><input type="image" src="/collection/images/arrow-r.png" height="15" width="24" name="pt_search:remove:0" /></td>
+       </tr>
+       <tr>
+        <td width="14"><input type="image" height="10" width="14" alt="up" src="/collection/images/button-up.png" name="pt_search:move_up:1" /></td>
+        <td width="14"><img height="10" width="14" alt="" src="/collection/images/button-nil.png" /></td>
+        <td width="90%" class="lighter">sars_china</td>
+        <td width="5%"><input type="image" src="/collection/images/arrow-r.png" height="15" width="24" name="pt_search:remove:1" /></td>
+       </tr>
+      </table>
+     </td>
+     <td width="50%" valign="top" class="pt-search">
+      <table width="100%" class="pt-search" border="0" cellspacing="0">
+       <tr>
+        <th colspan="3" align="left">Form Search</th>
+       </tr>
+       <tr>
+        <td width="5%"></td>
+        <td><input type="text" name="pt_search.search_term" value="" /><input type="submit" class="butt" name="pt_search:search" value="Search" /><input type="submit" class="butt" name="pt_search:clear" value="Clear" /></td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">pt-sel</h2>
+   <table width="100%" class="pt-sel">
+    <tr>
+     <th width="50%">Selected</th>
+     <th width="50%">Available</th>
+    </tr>
+    <tr>
+     <td>
+      <select size="5" multiple name="group_edit.exclude_group">
+       <option value="0">Group A</option>
+       <option value="1">Group B</option>
+      </select><br />
+      <input class="butt" type="submit" name="group_edit:remove" value="Remove >>" />
+     </td>
+     <td>
+      <select size="5" multiple name="group_edit.include_group">
+       <option value="0">Group C</option>
+      </select><br />
+      <input class="butt" type="submit" name="group_edit:add" value="<< Add" />
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">duperes</h2>
+   <table class="duperes">
+    <thead>
+     <tr class="top">
+      <td align="right">Source:</td>
+      <th>A</th>
+      <th>B</th>
+      <th>Edit</th>
+      <th>Clear</th>
+     </tr>
+    </thead>
+    <tbody>
+     <tr class="warn">
+      <th class="label"><label for="personmerge.person.surname">Surname</label></th>
+      <td><input type="radio" name="personmerge.fields[0].source" value="a" checked />SMITH</td>
+      <td><input type="radio" name="personmerge.fields[0].source" value="b" />SMITH</td>
+      <td class="user"><input id="personmerge.person.surname" name="personmerge.person.surname" /></td>
+      <td align="center"><input type="radio" name="personmerge.fields[0].source" value="d" /></td>
+     </tr>
+     <tr>
+      <th class="label"><label for="personmerge.person.state">State</label></th>
+      <td></td>
+      <td><input type="radio" name="personmerge.fields[12].source" value="b" checked />New South Wales</td>
+      <td class="user">
+       <select id="personmerge.person.state" name="personmerge.person.state">
+        <option value="">Unknown</option>
+        <option value="ACT">Australian Capital Territory</option>
+        <option value="Lost">Lost</option>
+        <option value="NSW">New South Wales</option>
+        <option value="NT">Northern Territory</option>
+        <option value="Other">Other</option>
+        <option value="QLD">Queensland</option>
+        <option value="SA">South Australia</option>
+        <option value="TAS">Tasmania</option>
+        <option value="VIC">Victoria</option>
+        <option value="WA">Western Australia</option>
+       </select>
+      </td>
+      <td align="center"><input type="radio" name="personmerge.fields[12].source" value="d" /></td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">searchres</h2>
+   <table width="100%" border="0" class="searchres" cellspacing="0">
+    <tbody>
+     <tr class="person">
+      <td>SMITH, John, 01/01/1941 (67 yrs), Male, State: New South Wales</td>
+      <td class="buttcol" align="right"> </td>
+     </tr>
+     <tr class="case">
+      <td>SARS, Status: Excluded, System ID: 44</td>
+      <td class="buttcol" align="right"><input type="submit" class="butt" idexp="name" name="edit_case:44" value="Edit Case" /></td>
+     </tr>
+     <tr class="case">
+      <td>SARS, System ID: 53, Onset Date: 01/02/2003 04:05:06</td>
+      <td class="buttcol" align="right"><input type="submit" class="butt" idexp="name" name="edit_case:53" value="Edit Case" /></td>
+     </tr>
+     <tr class="contact">
+      <td>SARS contact, System ID: 105</td>
+      <td class="buttcol" align="right"><input type="submit" class="butt" idexp="name" name="edit_contact:7:105" value="Edit Contact" /></td>
+     </tr>
+     <tr class="contactcase">
+      <td>Contact of: SARS, Status: Preliminary, Local ID: ABC, System ID: 7, Onset Date: 16/01/2008 00:00:00 - SMITH, Jane, 45 yrs, Female, Locality/Suburb: LAMPTON, State: New South Wales</td>
+      <td class="buttcol"></td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">searchres</h2>
+   <table class="task" width="95%" cellspacing="0" border="0">
+    <tr>
+     <th colspan="2" align="left">Creating a new task for this case:</th>
+    </tr>
+    <tr><td colspan="2" class="fauxrule"></td></tr>
+    <tr>
+     <td width="30%"><label for="task_description">Task Description</label></td>
+     <td><input class="fill" id="task_description" name="edittask.task_description" value="" /></td>
+    </tr>
+    <tr>
+     <td align="right">or <input type="submit" class="bigbutt" name="apply_desc" value="Apply Description" /></td>
+     <td>
+      <select class="fill" id="task_descriptions" name="popular_tasks">
+       <option>Get onset date</option>
+       <option>Get exposure history</option>
+       <option>Get contact details for user.</option>
+      </select>
+     </td>
+    </tr>
+    <tr><td colspan="2" class="fauxrule"></td></tr>
+    <tr>
+     <td>Assign task to</td>
+     <td>
+      <table width="100%">
+       <tr>
+        <td><input type="radio" id="assign_me" name="edittask.assignee.assign_type" value="me" checked /></td>
+        <td width="40%"><label for="assign_me">Me</label></td>
+        <td width="40%"></td>
+        <td align="right"></td>
+       </tr>
+       <tr>
+        <td><input type="radio" id="assign_myunit" name="edittask.assignee.assign_type" value="myunit" /></td>
+        <td width="40%"><label for="assign_myunit">My unit</label></td>
+        <td width="40%"></td>
+        <td align="right"></td>
+       </tr>
+       <tr>
+        <td><input type="radio" id="assign_queue" name="edittask.assignee.assign_type" value="queue" /></td>
+        <td width="40%"><label for="assign_queue">Task queue</label></td>
+        <td width="40%">
+         <select class="fill" name="edittask.assignee.queue_id">
+          <option value="1">Queue A</option>
+          <option value="2">Queue B</option>
+          <option value="3">Queue C</option>
+         </select>
+        </td>
+        <td align="right"></td>
+       </tr>
+       <tr>
+        <td><input type="radio" id="assign_user" name="edittask.assignee.assign_type" value="user" /></td>
+        <td width="40%"><label for="assign_user">Another user</label></td>
+        <td width="40%"></td>
+        <td align="right"><input type="submit" class="bigbutt" name="search_user" value="Select User" /></td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">casecontacts</h2>
+   <table border="0" class="casecontacts" cellspacing="0">
+    <thead>
+     <tr><td colspan="5"></td></tr>
+     <tr>
+      <th colspan="5" align="right">Order by:
+       <select onchange="submit();" name="contacts.order">
+        <option selected>Surname</option>
+        <option>Given Names</option>
+       </select>
+       <input type="submit" class="butt" name="refetch" value="Update" />
+      </th>
+     </tr>
+     <tr>
+      <th>Person</th>
+      <th>ID</th>
+      <th>Date</th>
+      <th>Case/Contact Definition</th>
+      <th></th>
+     </tr>
+    </thead>
+    <tr class="lighter">
+     <td>BLAKE, John</td>
+     <td>145</td>
+     <td>1/2/2007</td>
+     <td>SARS followup</td>
+     <td class="buttcol"><input type="submit" class="butt" name="edit:145" value="Edit" /></td>
+    </tr>
+    <tr class="darker">
+     <td>SMITH, Jack, Male</td>
+     <td>54</td>
+     <td>None</td>
+     <td>SARS followup</td>
+     <td class="buttcol"><input type="submit" class="butt" name="edit:54" value="Edit" /></td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">choosenewcontact</h2>
+   <table class="choosenewcontact" border="0">
+    <tr>
+     <td class="name">SARS followup</td>
+     <td>Sars Followup</p></td>
+     <td class="buttcol"><input type="submit" class="bigbutt" name="new:3" value="New Contact" /></td>
+    </tr>
+    <tr>
+     <td class="name">SARS confirmed contact</td>
+     <td>Contirm SARS contact - additional measures required</td>
+     <td class="buttcol"><input type="submit" class="bigbutt" name="new:3" value="New Contact" /></td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">adminusers</h2>
+   <table class="adminusers sorttable">
+    <thead>
+     <tr>
+      <th class="sortable">Enabled</th>
+      <th class="sortable">Bad Pass</th>
+      <th class="sortable">Username</th>
+      <th class="sortable defaultsort">Full name</th>
+      <th class="sortable">Role</th>
+      <th></th>
+     </tr>
+    </thead>
+    <tbody>
+     <tr>
+      <td>Yes</td>
+      <td></td>
+      <td>admin</td>
+      <td>Administrator</td>
+      <td>Administrator</td>
+      <td><input type="submit" class="smallbutt" name="edit:0" value="Edit" /><input type="submit" class="smallbutt" name="log:0" value="Log" /></td>
+     </tr>
+     <tr class="darker">
+      <td>Yes</td>
+      <td></td>
+      <td>jsmith</td>
+      <td>John Smith</td>
+      <td>Admin</td>
+      <td><input type="submit" class="smallbutt" name="edit:21344" value="Edit" /><input type="submit" class="smallbutt" name="log:21344" value="Log" /></td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">ltab</h2>
+   <table class="ltab" cellpadding="0" cellspacing="0">
+    <tr>
+     <td class="tabs">
+      <input type="submit" accesskey="T" name="tab:title" value="Title" /><br>
+      <input type="submit" class="selected" disabled name="tab:filters" value="Filters" /><br>
+      <br>
+      <input class="act" type="submit" name="report" value="Report" /><br>
+      <input type="submit" accesskey="V" name="tab:vis" value="Visualise" /><br>
+     </td>
+     <td class="rcontent">
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">admin-m</h2>
+   <table border="0" class="admin-m">
+    <tr>
+     <td rowspan="4" class="op">Users</td>
+     <td class="ilabel"><label for="user_search_name">by user name</label></td>
+     <td class="inputs"><input id="user_search_name" class="inputs" type="text" name="user_search.name" value="" /></td>
+     <td rowspan="4" align="left">
+      <label><input type="radio" id="user_search_enabled" name="user_search.enabled" value="enabled" checked />Enabled</label><br>
+      <label><input type="radio" id="user_search_disabled" name="user_search.enabled" value="disabled" />Disabled</label><br>
+      <label><input type="radio" id="user_search_both" name="user_search.enabled" value="either" />Either</label><br>
+      <label><input type="radio" id="user_search_deleted" name="user_search.enabled" value="deleted" />Deleted</label><br>
+     </td>
+     <td rowspan="4" class="buttons">
+      <input class="smallbutt" type="submit" name="user_search_go" value="Find" /><br />
+      <input class="smallbutt" type="submit" name="user_add" value="New" />
+     </td>
+    </tr>
+    <tr>
+     <td class="ilabel"><label for="user_search.fullname">by full name</label></td>
+     <td class="inputs"><input id="user_search.fullname" class="inputs" type="text" name="user_search.fullname" value="" /></td>
+    </tr>
+    <tr>
+     <td class="ilabel"><label for="user_search.role">by role</label></td>
+     <td class="inputs"><input id="user_search.role" class="inputs" type="text" name="user_search.role" value="" /></td>
+    </tr>
+    <tr>
+     <td class="ilabel"> <label for="user_search.unit">by unit</label></td>
+     <td class="inputs"><input id="user_search.unit" class="inputs" type="text" name="user_search.unit" value="" /></td>
+    </tr>
+    <tr><td></td><td colspan="3" class="err">error</td></tr>
+    <tr><td class="spacer" colspan="5"></td></tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">gridtab</h2>
+   <table border="0" class="gridtab">
+    <thead class="darkest">
+     <tr>
+      <th nowrap> Selected</th>
+      <th nowrap><img src="/collection/images/sortdnsel.png" /><input type="image" src="/collection/images/sortup.png" name="page:orderby:name_desc" /> Name</th>
+      <th nowrap><input type="image" src="/collection/images/sortdn.png" name="page:orderby:street_address" /><input type="image" src="/collection/images/sortup.png" name="page:orderby:street_address_desc" /> Street Address</th>
+      <th nowrap><input type="image" src="/collection/images/sortdn.png" name="page:orderby:enabled" /><input type="image" src="/collection/images/sortup.png" name="page:orderby:enabled_desc" /> Enabled</th>
+      <th nowrap>   Context</th>
+      <th style="text-align: center;"> <input class="smallbutt" type="submit" name="add:" value="New" /></th>
+     </tr>
+    </thead>
+    <tfoot class="darkest">
+     <tr>
+      <td colspan="7" align="left" class="darkest">
+       <table width="100%" cellspacing="0" cellpadding="0" border="0">
+        <tr>
+         <td align="left">
+          Select
+          <input class="smallbutt" type="submit" name="select_all" value="All" /><input class="smallbutt" type="submit" name="select_none" value="None" />
+         </td>
+         <td align="center">
+          Selected role to/from context<select name="select_group_id"><option value="0">AAA</option><option value="1">BBB</option></select>
+          <input class="butt" type="submit" name="select_group:add" value="Add" /><input class="butt" type="submit" name="select_group:del" value="Remove" />
+         </td>
+        </tr>
+       </table>
+      </td>
+     </tr>
+    </tfoot>
+    <tbody>
+     <tr class="darker">
+      <td><input type="checkbox" name="selected" value="4002" /></td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td align="center" nowrap>
+       <input class="smallbutt" type="submit" name="edit:4002" value="Edit" /></td>
+     </tr>
+     <tr>
+      <td><input type="checkbox" name="selected" value="0" /></td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td align="center" nowrap>
+       <input class="smallbutt" type="submit" name="edit:0" value="Edit" /></td>
+     </tr>
+     <tr class="darker">
+      <td><input type="checkbox" name="selected" value="1" /></td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td>xxxx</td>
+      <td align="center" nowrap>
+       <input class="smallbutt" type="submit" name="edit:1" value="Edit" /></td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">admin-u</h2>
+   <table border="0" class="admin-u">
+    <tr>
+     <td colspan="2">
+      <table width="100%" cellpadding="0" cellspacing="0" border="0">
+       <tr>
+        <th width="100%">Edit user</th>
+        <th align="right"><input type="submit" class="butt" name="view_log" value="View Log" /></th>
+       </tr>
+      </table>
+     </td>
+    </tr>
+    <tr>
+     <td align="right">Username:</td>
+     <td><input size="40" name="user.username" value="jsmith" /></td>
+    </tr>
+    <tr>
+     <td align="right">Enabled:</td>
+     <td align="left">
+      <input id="user_enabled" type="radio" name="user.enabled" value="True" checked />Yes 
+      <input id="user_disabled" type="radio" name="user.enabled" value="" />No
+     </td>
+    </tr>
+    <tr>
+     <td align="right">Locked due to bad password:</td>
+     <td>
+      <table width="100%" cellspacing="0" cellpadding="0" border="0">
+       <tr>
+        <td align="left" width="100%">No</td>
+        <td>
+         <input type="submit" class="butt" name="reset_attempts" value="Reset" /></td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+    <tr>
+     <td align="right">Rights:</td>
+     <td align="left">
+      <table width="100%" class="gridtab" cellpadding="2" style="text-align: center;">
+       <tr>
+        <th>Unit A</th>
+        <th>User</th>
+        <th style="text-align: left;" width="100%">Right</th>
+       </tr>
+       <tr class="darker">
+        <td><input type="checkbox" disabled name="unitrights" value="on" /></td>
+        <td><input type="checkbox" name="rights" value="ADMIN" /></td>
+        <td align="left">System Administrator</td>
+       </tr>
+       <tr class="darker">
+        <td><input type="checkbox" disabled name="unitrights" value="on" /></td>
+        <td><input type="checkbox" name="rights" value="UNITADMIN" /></td>
+        <td align="left">Unit Administrator</td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+    <tr>
+     <td align="right">Unit:</td>
+     <td>
+      <table width="100%" bgcolor="black" border="0" cellpadding="0">
+       <tr>
+        <td width="50%" valign="top" class="pt-search">
+         <table width="100%" border="0" cellspacing="0">
+          <tr>
+           <th colspan="3" align="left">Units</th>
+          </tr>
+          <tr>
+           <td width="24"></td>
+           <td width="90%" class="lighter">Unit A</td>
+           <td width="5%"><input type="image" src="/collection/images/arrow-r.png" height="15" width="24" name="pt_search:remove:0" /></td>
+          </tr>
+         </table>
+        </td>
+        <td width="50%" valign="top" class="pt-search">
+         <table width="100%" class="pt-search" border="0" cellspacing="0">
+          <tr>
+           <th colspan="3" align="left">Add/Search</th>
+          </tr>
+          <tr>
+           <td width="5%"></td>
+           <td><input type="text" name="pt_search.search_term" value="" /><input type="submit" class="butt" name="pt_search:search" value="Search" /><input type="submit" class="butt" name="pt_search:clear" value="Clear" /></td>
+          </tr>
+         </table>
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">admin-syndromes</h2>
+   <table border="0" class="admin-syndromes toptable" width="95%">
+    <tr>
+     <th colspan="3">
+      <table width="100%" cellspacing="0" cellpadding="0">
+       <tr>
+        <td align="left">Edit Case/Contact Definition</td>
+        <td align="right">
+         <input class="bigbutt" type="submit" name="case_status" value="Status Values" />
+         <input class="bigbutt" type="submit" name="demog_fields" value="Demographic Fields" />
+        </td>
+       </tr>
+      </table>
+     </th>
+    </tr>
+    <tr>
+     <td><label for="syndrome.name">Name</label></td>
+     <td colspan="2"><input size="60" name="syndrome.name" value="SARS" /></td>
+    </tr>
+    <tr>
+     <td>
+      <label for="syndrome.description">Description<br>
+      <input class="butt" type="submit" name="wikiedit:description" value="Edit" /></label>
+     </td>
+     <td colspan="2"><div class="preview">Severe Acute Respiratory Syndrome</p></div></td>
+    </tr>
+    <tr>
+     <td><label>Forms</label></td>
+     <td colspan="2">
+     <table class="pt-search">
+   <tr class="title">
+    <th>Selected</th>
+    <th>Available</th>
+    <td>
+     <input type="submit" class="butt" name="pt_search:clear" value="Clear" /></td>
+   </tr>
+   <tr>
+    <td></td>
+    <td>
+     <input type="text" style="width:98%;" name="pt_search.search_term" value="sars*" /></td>
+    <td>
+     <input type="submit" class="butt" name="pt_search:search" value="Search" /></td>
+   </tr>
+   <tr>
+    <td class="pane">
+     <table>
+      <tr>
+        <td width="14">
+          <img height="10" width="14" alt="" src="/collection/images/button-nil.png" /></td>
+         <td width="14">
+          <input type="image" height="10" width="14" alt="dn" src="/collection/images/button-down.png" name="pt_search:move_dn:0" /></td>
+        <td width="90%">
+         sars_exposure</td>
+        <td width="5%">
+         <input type="image" src="/collection/images/arrow-r.png" height="15" width="24" name="pt_search:remove:0" /></td>
+       </tr>
+      <tr>
+        <td width="14">
+          <input type="image" height="10" width="14" alt="up" src="/collection/images/button-up.png" name="pt_search:move_up:1" /></td>
+         <td width="14">
+          <img height="10" width="14" alt="" src="/collection/images/button-nil.png" /></td>
+        <td width="90%">
+         sars_china</td>
+        <td width="5%">
+         <input type="image" src="/collection/images/arrow-r.png" height="15" width="24" name="pt_search:remove:1" /></td>
+       </tr>
+      </table>
+    </td>
+    <td class="pane" colspan="2">
+     <table>
+       <tr>
+         <td width="5%" nowrap>
+          <input type="image" src="/collection/images/arrow-l.png" height="15" width="24" name="pt_search:add:2" /></td>
+         <td>
+          sars_china</td>
+         <td>
+          </td>
+        </tr>
+       <tr>
+         <td width="5%" nowrap>
+          <input type="image" src="/collection/images/arrow-l.png" height="15" width="24" name="pt_search:add:0" /></td>
+         <td>
+          sars_exposure</td>
+         <td>
+          </td>
+        </tr>
+       <tr>
+         <td width="5%" nowrap>
+          <input type="image" src="/collection/images/arrow-l.png" height="15" width="24" name="pt_search:add:1" /></td>
+         <td>
+          sars_followup_b</td>
+         <td>
+          </td>
+        </tr>
+       </table>
+     </td>
+   </tr>
+  </table>
+     </td>
+    </tr>
+    <tr>
+     <td><label>Groups</label></td>
+     <td colspan="2">
+      <table width="100%" class="pt-sel darker">
+       <tr>
+        <th width="50%">Selected</th>
+        <th width="50%">Available</th>
+       </tr>
+       <tr>
+        <td>
+         <select size="5" multiple name="group_edit.exclude_group">
+          <option value="0">Group A</option>
+          <option value="1">Group B</option>
+         </select><br />
+         <input class="butt" type="submit" name="group_edit:remove" value="Remove >>" />
+        </td>
+        <td>
+         <select size="5" multiple name="group_edit.include_group">
+          <option value="0">Group C</option>
+         </select><br />
+         <input class="butt" type="submit" name="group_edit:add" value="<< Add" />
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </table>
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">admin-fe</h2>
+   todo
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">admin-fe-work</h2>
+   todo
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">f-deploy</h2>
+   todo
+  </div>
+
+  <div class="sg">
+   <h2 class="sgsg">dupecfg</h2>
+   todo
+  </div>
+
+
+ </body>
+</html>
+
diff --git a/app/style.css b/app/style.css
new file mode 100644
index 0000000..25da4e3
--- /dev/null
+++ b/app/style.css
@@ -0,0 +1,1919 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation, Australian
+ *  Government Department of Health and Ageing, and others.
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+body {
+    margin: 0px;
+    background-color: white;
+    color: black;
+    font-size: 10pt;
+    font-family: "Verdana", "Arial", sans-serif;
+}
+
+input, textarea, select {
+    font-size: 100%;
+    font-family: "Verdana", "Arial", sans-serif;
+}
+input[type="submit"], input[type="button"] {
+    padding: 1px 2px;
+}
+input:focus, textarea:focus {
+    background-color: #fffae7;
+}
+
+/* Banner */
+
+.pagebanner {
+    width: 100%;
+    background: #f8f8f8 url('/{{APPNAME}}/images/title-gradient.png') repeat-y top right;
+    border-collapse: collapse;
+    border-bottom: 1px solid #ccc;
+}
+.pagebanner .logo img {
+    margin: 0 1em;
+    border: none;
+}
+.pagebanner .title {
+    width: 100%;
+    vertical-align: middle;
+    color: #888;
+}
+.pagebanner .title .apptitle {
+    font-size: 30px;
+    font-style: italic;
+}    
+.pagebanner .title .subbanner {
+    font-size: 10px;
+    font-style: italic;
+}
+    
+.pagebanner td {
+    font-size: 110%;
+    vertical-align: middle;
+    padding-right: 6px;
+    padding-left: 6px;
+}
+.pagebanner .quick-search {
+    width: 8em;
+}
+.pagebanner .sublink {
+    background-color: #ccf;
+}
+
+/* Common page elements */
+.err {
+    font-size: 110%;
+    font-weight: bold;
+    color: #f88;
+    background-color: inherit;
+}
+
+.reverr {
+    font-size: 110%;
+    font-weight: bold;
+    color: white;
+    background-color: #f88;
+    text-align: center;
+    border: 1px solid red;
+}
+
+.reverr input {
+    width: 18ex;
+}
+
+.redbox {
+    border: 2px solid red;
+    text-align: center;
+    margin: 0.2ex 1em 0.2ex 1em;
+    background: #fee;
+    color: #800;
+}
+
+.boxed {
+    display: block;
+    width: 100%;
+    border: 1px solid #ccc;
+    background-color: #eee;
+    padding: 0.2ex 1ex 0.2ex 1ex;
+}
+
+.right {
+    display: block;
+    float: right;
+}
+
+.deleted {
+    background: url("/{{APPNAME}}/images/deletedbg.png") repeat scroll;
+}
+
+.required {
+    font-weight: bold;
+    color: #c33;
+}
+
+.onerequired {
+    font-weight: bold;
+    color: #fc0;
+}
+
+.preview {
+    max-height: 20ex;
+    overflow: auto;
+    border: 1px inset #ccc;
+}
+
+.selector {
+    border: 2px solid #fc0;
+    margin: -2px;
+    background: #fea;
+}
+
+button#help {
+    background-color: transparent;
+    border: none;
+}
+
+.wikitext p, .wikitext ul {
+    margin: 0 0 1ex 0;
+}
+
+.wikihelp {
+    border: 2px solid #800;
+    background: #fee;
+    font-weight: bold;
+    font-size: 1em;
+    width: 2em !important;
+    color: #800;
+    padding: 2px;
+    margin: 2px;
+}
+.wikihelp:hover {
+    cursor: pointer;
+}
+
+.page-select {
+    width: 100%;
+}
+
+/* Delete buttons */
+.danger {
+    background: #fbb;
+    border: 2px outset red;
+    padding-left: 0.5ex;
+    padding-right: 0.5ex;
+}
+
+.smaller {
+    font-size: 83%;
+    font-weight: normal;
+}
+
+a:link, a:visited {
+    text-decoration: underline;
+    color: black;
+    background-color: inherit;
+}
+
+a:hover {
+    text-decoration: underline;
+}
+.input-hint {
+    color: #888;
+}
+
+/* submit button pretending to be a link */
+.sublink {
+    border: none;
+    color: #008;
+    background: white;
+    font-weight: bold;
+    width: auto;
+    padding: 0 !important;
+    margin: 0 !important;
+    overflow: visible;                 /* Otherwise padding is erratic in IE */
+}
+.sublink:hover {
+    text-decoration: underline;
+}
+.sublink:focus {
+    background: white !important;
+}
+/* link that submits */
+.linksub:link, .linksub:visited {
+    color: #008 !important;
+    font-weight: bold;
+    text-decoration: none;
+    padding: 0 1em;
+}
+.linksub:hover {
+    text-decoration: underline;
+}    
+.clickable:hover, .clicktab td:hover {
+    background-color: #fffae7 !important;
+    cursor: pointer;
+}
+.foldicon {
+    display: none;
+    position: relative;
+    width: 1em;
+    float: left;
+    font-weight: bold;
+    text-align: center;
+    color: #88f;
+}
+.toptable td, .toptable th {
+    vertical-align: top;
+}
+
+.buttonbar {
+    padding: 0;
+}
+.buttonbar table {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+}    
+.buttonbar .input {
+    font-size: 83%;
+}
+.buttonbar .ll {
+    text-align: left;
+    width: 33%;
+}    
+.buttonbar .mm {
+    text-align: center;
+    width: 34%;
+}
+.buttonbar .rr {
+    text-align: right;
+    width: 33%;
+}
+.menubar {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    background: #ddf;
+    border: solid #aaa;
+    border-width: 1px 0px;
+}
+.menubar td {
+    vertical-align: middle;
+    white-space: nowrap;
+    padding: 0;
+    height: 3ex;
+}
+.menubar input, .menubar select {
+    padding: 0;
+    margin: 0;
+    font-size: 90% !important;
+}
+.menubar .sublink {
+    background-color: #ddf !important;
+    margin: 0 1em !important;
+}
+.menubar .mbsb {
+    min-width: 2em;
+}
+.menubar .mbl {
+    text-align: left;
+    width: 25%;
+}
+.menubar .mbm {
+    text-align: center;
+    width: 50%;
+}
+.menubar .mbr {
+    text-align: right;
+    width: 25%;
+}
+.menubar .mbp {
+    margin: auto;
+    border-collapse: collapse;
+}    
+.menubar .mbp .mbpt {
+    padding: 0 0.2em;
+    background-color: #bbf;
+    color: #44f;
+}
+    
+/* By default, disabled inputs are hard to read in Firefox, this tweak makes
+ * them more readable, but renders Safari, Opera and Konqueror unreadable:
+ * input[disabled], select[disabled], textarea[disabled] {
+ *    background-color: #eee;
+ *    color: #444;
+ *    border: 1px dotted #444;
+ *}
+ */
+
+.content {
+    margin: 1ex;
+}
+.scroll-content {
+    margin: 1ex;
+    overflow: auto;
+}
+
+
+.pagetitle {
+    margin: 1ex 1em;
+    border-bottom: 1px solid #888;
+    font-size: 120%;
+    font-weight: bold;
+}
+
+h3 {
+    font-size: 100%;
+    font-weight: bold;
+    margin: 0.5ex 0 0.5ex 0;
+}
+
+.copyright {
+    margin-top: 1ex;
+    font-size: 83%;
+}
+
+.fauxrule {
+    height: 1px;
+    background-color: #f9c;
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+.debug {
+    clear: both;
+    font-size: 83%;
+    background-color: #eee;
+    color: #c88;
+    border-top: 1px solid #888;
+    margin: 1ex;
+    padding: 1ex;
+}
+
+.buttons {
+    margin: 0;
+    padding: 0;
+}
+
+/* Four "standardised" button sizes */
+.smallbutt {
+    font-size: 83%;
+    width: 4em;
+}
+.butt {
+    font-size: 83%;
+    width: 7.2em;
+}
+.bigbutt {
+    font-size: 83%;
+    width: 10em;
+}
+.file {
+    font-size: 83%;
+}
+
+.err-msg {
+    color: #c00;
+    background-color: #fee;
+    border: 1px solid #c00;
+    border-left: 0.5em solid #c00;
+    margin-top: 0.5ex;
+    font-weight: bold;
+    padding-left: 0.5em;
+}
+.warn-msg {
+    color: #e80;
+    background-color: #fed;
+    border: 1px solid #e80;
+    border-left: 0.5em solid #e80;
+    margin-top: 0.5ex;
+    font-weight: bold;
+    padding-left: 0.5em;
+}
+.info-msg {
+    color: #080;
+    background-color: #efe;
+    border: 1px solid #080;
+    border-left: 0.5em solid #080;
+    margin-top: 0.5ex;
+    font-weight: bold;
+    padding-left: 0.5em;
+}
+
+.adminbox {
+    color: #888;
+    background-color: #ddd;
+    border: 1px solid #888;
+    border-top: none;
+    border-left: 1ex solid #888;
+    padding-left: 1ex;
+    padding-right: 1ex;
+}
+
+/* "Dialog" for confirming non-reverseable actions */
+.centbox {
+    width: 70%;
+    background-color: #fcc;
+    border: 2px solid red;
+    color: #800;
+    text-align: center;
+    margin: 3ex auto 3ex auto;
+    padding-bottom: 2ex;
+    font-size: 110%;
+}
+.centbox h1 {
+    border-bottom: 2px solid red;
+    background-color: #f88;
+    font-size: 100%;
+    padding: 0;
+    margin: 0;
+    margin-bottom: 2ex;
+}
+.centbox .file {
+    width: auto;
+}
+.centbox .quote {
+    width: 80%;
+    margin: 1ex auto 1ex auto;
+    background-color: #fee;
+    border: 2px solid #800;
+    padding: 1ex;
+}
+.centbox .quote h1 {
+    background: inherit;
+    color: inherit;
+    margin: 0;
+    border: none;
+}
+
+.key {
+    display: block;
+    float: right;
+    text-align: left;
+    font-size: 73%;
+    width: 20em;
+    padding: 0 1em 1em 1em;
+}
+
+.key thead th {
+    text-align: center;
+}
+.key th, .key td {
+    vertical-align: top;
+    text-align: left;
+}
+
+
+/* Home page elements */
+.helpdesk {
+    font-style: italic;
+    font-size: 83%;
+    padding-left: 1ex;
+}
+
+/* Left 20% Right 80% two column layout, absolutely position (no size) */
+.left20 {
+    position: absolute;
+    height: 100%;
+    width: 20%;
+}
+.right80 {
+    position: absolute;
+    width: 80%;
+    left: 20%;
+}
+/* Two column, 50:50 */
+.twocols {
+    width: 100%;
+    border-collapse: collapse;
+}
+.twocols .leftcol {
+    width: 50%;
+    vertical-align: top;
+    padding: 0 0.5ex 0 1ex;
+}
+.twocols .rightcol {
+    width: 50%;
+    vertical-align: top;
+    padding: 0 1ex 0 0.5ex;
+}
+
+/* Home page */
+
+.userdetails {
+    border-bottom: 2px solid #ddd;
+    background: #eee;
+    white-space: nowrap;
+    width: 100%;
+    font-size: 83%;
+    border-collapse: collapse;
+}
+.userdetails label {
+    font-weight: bold;
+}
+.userdetails td {
+    padding: 2px 1ex;
+    white-space: nowrap;
+}
+.userdetails select {
+    border: none;
+    background: #eee;
+}
+/*      syndromes */
+.syndlist {
+    padding: 0 1em 0 1.5em;
+}
+.syndlist .syndrome {
+    margin: 2ex 0;
+}
+.syndlist .syndrome .foldicon {
+    position: absolute;
+    margin-left: -1em;
+    width: 1em;
+    font-weight: bold;
+    font-size: 120%;
+}
+.syndlist .syndrome .info {
+    float: right;
+    margin: -3px 1em 0 0;
+}
+.syndlist .syndrome h1 {
+    font-size: 120%;
+    margin: 0 2.5em 0 0;
+    border-bottom: 1px solid #ccc;
+}
+.syndlist .syndrome .actions {
+    background-color: #f8f8f8;
+}
+.syndlist .syndrome .detail {
+    clear: both;
+}
+.syndlist .syndrome .count {
+    font-size: 83%;
+    clear: both;
+}
+.syndlist .syndrome .actions .sl-action {
+    float: left;
+    color: #88c;
+    margin-right: 2em !important;
+    position: relative;
+}
+.sl-action .sla-more {
+    font-size: 90%;
+}
+.sl-action .sublink {
+    color: #88c;
+    left: 0;
+}
+.sl-action .sublink:hover {
+    background-color: #fffae7 !important;
+    text-decoration: underline !important;
+}
+.sl-action .mb_items {
+    display: none;
+    white-space: nowrap;
+    position: absolute;
+    border: 1px solid #88c;
+    background: #f8f8f8;
+    padding: 1px;
+    outline: 0;
+}
+.sl-action .mb_items input {
+    margin: 0;
+    padding: 0;
+    background: #f8f8f8;
+}
+
+.top-tasks {
+    width: 100%;
+    border-collapse: collapse;
+}
+.top-tasks th {
+    border-bottom: 1px solid #ccc;
+    text-align: left;
+    font-size: 120%;
+    padding: 0;
+}
+.top-tasks td {
+    padding: 0;
+    vertical-align: baseline;
+}
+.top-tasks .desc {
+    padding-left: 1em;
+    color: #824;
+}
+    
+.recent-activity {
+    width: 100%;
+    border-collapse: collapse;
+}
+.recent-activity th {
+    border-bottom: 1px solid #ccc;
+    text-align: left;
+    font-size: 120%;
+    padding: 0;
+}
+.recent-activity td {
+    padding: 0;
+    vertical-align: baseline;
+}
+
+
+/* Search and case details - two columns of label/input pairs */
+.labeltext {
+    width: 100%;
+}
+.labeltext .label {
+    padding-left: 1em;
+    width: 20%;
+    vertical-align: top;
+}
+.labeltext .field {
+    background: #eee;
+    border: 1px solid #ccc;
+    width: 30%;
+    vertical-align: top;
+}
+.labelform {
+    width: 100%;
+}
+.labelform .label {
+    width: 20%;
+    vertical-align: top;
+}
+.labelform .label label {
+    margin-top: 0.3ex;
+    margin-left: 1em;
+    display: block;
+}
+.labelform .field {
+    width: 30%;
+    vertical-align: top;
+}
+.labelform .field input, .labelform .field select, .labelform .field textarea {
+    width: 98%;
+}
+.labelform .field .autowidth {
+    width: auto;
+}
+
+.labelform .field .short {
+    width: 10em;
+    float: left;
+    margin-right: 1em;
+}
+.labelform .grouplabel th {
+    text-align: left;
+    color: #888;
+    background: #f8f8f8;
+    border-bottom: 1px solid #ddd;
+}
+.fieldandbutt {
+    position: relative;
+}
+.fieldandbutt .fieldand {
+    margin-right: 5em;
+    padding-right: 2px;
+}
+.fieldandbutt .fieldand input {
+    width: 98% !important;
+}
+.fieldandbutt .andbutt {
+    position: absolute;
+    right: 0;
+    bottom: 1px;
+    width: 5em;
+}
+.fieldandbutt .andbutt input {
+    width: 100% !important;
+    font-size: 83%;
+}
+
+/* user details - like the above but only one column of label/input pairs */
+.widelabelform {
+}
+.widelabelform td {
+    vertical-align: top;
+}
+.widelabelform th {
+    font-weight: bold;
+    background-color: #ccc;
+    padding-left: 1ex;
+    text-align: left;
+}
+.widelabelform .label {
+    background-color: #eee;
+    padding: 0 1ex 0 1ex;
+    border-right: 2px solid #ccc;
+    line-height: 3ex;
+}
+.widelabelform .field input, .widelabelform .field select, .widelabelform .field textarea {
+    width: 30em;
+}
+.widelabelform .butt {
+    font-size: 83%;
+    width: 7.2em;
+}
+.widelabelform .autowidth * {
+    width: auto;
+}
+
+.bulletin-list {
+    padding: 0 1em;
+    overflow: hidden;
+}
+.bulletin-list .item {
+    font-size: 83%;
+    margin: 0 0 4ex 0;
+}
+.bulletin-list .item h1 {
+    font-size: 120%;
+    font-weight: bold;
+    margin: 0.5ex 0;
+}
+
+.bulletin-list .more {
+    float: right;
+    vertical-align: text-bottom;
+}
+
+.bulletin-list .date {
+    clear: both;
+    font-style: italic;
+    float: right;
+    margin: 0.5ex 0;
+    color: #888;
+}
+
+.bulletin-list .synopsis {
+    clear: both;
+    color: #040;
+}
+
+.bulletin-detail {
+    margin: 4em;
+    padding: 4em;
+    width: 70%;
+    text-align: left;
+    background-color: #cfc;
+    border-left-width: 1px;
+    border-right-width: 1px;
+    border-top-width: 0;
+    border-bottom-width: 0;
+    border-style: dashed;
+    border-color: black;
+}
+
+.bulletin-detail .head {
+}
+
+.bulletin-detail .body {
+    background-color: #ddf;
+    color: black;
+}
+
+/* Centered login page */
+.login {
+    margin-top: 4em;
+    margin-left: 0;
+    margin-right: 10%;
+    empty-cells: show;
+}
+
+.login a {
+    text-decoration: none;
+}
+
+.login .lhs {
+    text-align: right;
+    padding-right: 1ex;
+    width: 30%;
+}
+
+.login .rhs {
+    padding-left: 1ex;
+    border-left: 2px solid #888;
+}
+
+.login .logo {
+    height: 12em;
+}
+
+.login .title {
+    font-size: 30pt;
+    padding-bottom: 0.5ex;
+    margin-bottom: 0.5ex;
+    border-bottom: 4px solid #888;
+}
+
+.login .subtitle {
+}
+
+.login .copyright {
+    font-size: 83%;
+}
+
+.login .prompt {
+}
+
+.login .prompt input, .login .prompt select {
+ width: 30ex;
+}
+
+.login .apply {
+ height: 5em;
+}
+.login .help {
+    font-size: 83%;
+}
+.login .dedication {
+    height: 10em;
+    font-size: 83%;
+}
+
+.login .logout {
+    background-color: #fcc;
+    border-left: 2px solid #f88;
+    padding: 0.5em;
+}
+
+.login .msg {
+    color: #080;
+    background-color: #efe;
+    border-left: 1ex solid #080;
+    padding: 0.5em;
+    margin-top: 0.5ex;
+}
+
+/* Forms list */
+.formslist {
+    width: 100%;
+    background-color: #fed;
+    font-size: 83%;
+}
+.formslist th {
+    background-color: #db8;
+    text-align: left;
+    padding-left: 1ex;
+    padding-right: 1ex;
+    border-top: 2px solid white;
+}
+.formslist .deleted {
+    font-size: 83%;
+    color: #888;
+}
+.formslist .formname {
+    font-weight: bold;
+}
+.formslist .formid {
+    background-color: #fed;
+    border-right: 1px solid #db8;
+    text-align: center;
+    white-space: nowrap;
+}
+.formslist td {
+    padding-left: 1ex;
+    padding-right: 1ex;
+    background-color: white;
+    vertical-align: baseline;
+    border-bottom: 2px solid #db8;
+}
+
+/* Syndrome form */
+.syndrome-form {
+}
+
+.syndrome-form .heading {
+    text-align: left;
+    font-size: 110%;
+    font-weight: bold;
+    background-color: #fbe;
+    border-bottom: 1px solid #f9c;
+}
+.syndrome-form .section {
+    text-align: left;
+    font-size: 110%;
+    font-weight: bold;
+    vertical-align: top;
+    background-color: #ffe6f9;
+}
+
+.syndrome-form .subsection {
+    text-align: left;
+    font-size: 110%;
+    font-weight: bold;
+    vertical-align: top;
+    background-color: #ffe6f9;
+}
+
+.syndrome-form .question0 {
+    text-align: left;
+}
+.syndrome-form .question1 {
+    text-align: left;
+    background-color: #fff7f7;
+}
+
+.syndrome-form .disabledq {
+    background-color: #ccc;
+}
+
+.syndrome-form .qtext {
+    vertical-align: top;
+}
+
+.syndrome-form .number {
+    vertical-align: top;
+    padding-right: 0.5em;
+}
+
+.syndrome-form .inputs {
+    vertical-align: top;
+}
+.syndrome-form .reqinp {
+    background-color: #eef;
+}
+.syndrome-form .errinp {
+    background-color: #fee;
+}
+.syndrome-form .error {
+    background-color: #fcc;
+    color: red;
+    font-size: 83%;
+}
+
+.syndrome-form .infobut {
+    float: right;
+    padding-right: 1ex;
+}
+
+.syndrome-form .info {
+    border: 1px solid gray;
+    background-color: #ddd;
+    padding: 1ex;
+}
+
+.syndrome-form .pretext, .syndrome-form .posttext {
+    font-size: 83%;
+}
+.syndrome-form .skiptext {
+    font-size: 83%;
+    font-weight: bold;
+    color: #008;
+}
+
+.syndrome-form .line {
+    background-color: #fdd;
+    padding: 0px;
+    margin: 0.5ex 0 0.5ex 0;
+    height: 1px;
+}
+
+.syndrome-form .submit {
+}
+
+/* Data export, select print forms */
+.selexp {
+    border-spacing: 12px;
+}
+.selexp td {
+    vertical-align: top;
+}
+.selexp .wide * {
+    width: 35em;
+}
+.selexp td td {
+    vertical-align: baseline;
+}
+
+/* Participation table search */
+.pt-search {
+    width: 100%;
+    border-collapse: collapse;
+    border: 1px solid #a8a;
+    background-color: #fcf;
+}
+.pt-search .title th, .pt-search .title td {
+    vertical-align: middle;
+}
+.pt-search .pane {
+    width: 50%;
+    vertical-align: top;
+}
+.pt-search .pane table {
+    width: 100%;
+    margin: 0;
+    background-color: #fdf;
+    border: 1px solid #a8a;
+}
+.pt-search .pane table td {
+    vertical-align: middle;
+}
+
+/* Participation table selector */
+.pt-sel {
+    width: 100%;
+    border: 1px solid #aaa;
+    border-collapse: collapse;
+}
+.pt-sel th {
+    width: 50%;
+    padding-left: 0.2em;
+}
+.pt-sel td {
+    text-align: center;
+}
+.pt-sel select {
+    width: 100%;
+}
+.pt-sel input {
+    width: 8em;
+}
+
+/* Dupe check results */
+.duperes {
+}    
+.duperes .gray {
+    color: #888;
+}
+.duperes tr {
+    background-color: #eef;
+}
+.duperes td table tr {
+    background-color: inherit;
+}
+.duperes .warn {
+    background-color: #fec;
+}
+.duperes th {
+    background-color: #bbf;
+    text-align: left;
+}
+.duperes th, .duperes td {
+    padding-left: 0.5ex;
+    padding-right: 0.5ex;
+}
+.duperes .indent {
+    width: 2em;
+    background-color: white;
+}
+.duperes .subhead th {
+    background-color: #ddf;
+    border-bottom: 1px solid #bbf;
+}
+.duperes .forminput {
+    padding: 0;
+}
+.duperes .forminput .syndrome-form {
+    margin: 0;
+    padding: 0;
+}
+.duperes .forminput .syndrome-form .reqinp {
+    background-color: #dff;
+}
+.duperes .darker {
+    background-color: #ddf;
+}
+.duperes .user input, .duperes .user select, .duperes .user textarea {
+    width: 20em;
+}
+.duperes .excluded {
+    font-size: 83%;
+    font-weight: bold;
+    background-color: #bbc;
+}
+.duperes .label {
+    padding-left: 1ex;
+    background-color: #ccf;
+    border-right: 2px solid #aaf;
+    text-align: left;
+}
+.duperes .op {
+    background-color: #bef;
+    text-align: center;
+    border: 2px solid #aaf;
+    padding: 0 1ex 0 1ex;
+}
+.duperes .top th {
+    text-align: center;
+    border-bottom: 2px solid #aaf;
+    background-color: #ccf;
+}
+
+.search {
+    border-left: 4px solid #f9c;
+    color: #a06;
+    margin-top: 1ex;
+}
+.search .flag {
+    font-size: 100%;
+    font-weight: bold;
+    background-color: #ffe8f8;
+    padding-left: 1em;
+    margin: 0;
+}
+.search .pickset td {
+    vertical-align: baseline;
+}
+.search .pickset .pspane {
+    width: 50%;
+    padding-left: 2em;
+}
+.search .pickset th {
+    text-align: left;
+    padding-left: 1em;
+}
+
+/* Search results */
+.searchinfo {
+    font-size: 83%;
+    clear: both;
+}
+.searchres {
+    background-color: #eef;
+    color: inherit;
+    empty-cells: show;
+}
+
+.searchres thead, .searchres tfoot {
+    background-color: #bbf;
+}
+.searchres th {
+    text-align: left;
+    border-top: 1px solid black;
+    color: inherit;
+}
+.searchres td {
+    vertical-align: top;
+}
+.searchres .darker {
+    background-color: #ddf;
+}
+.searchres .page-select {
+    background-color: #ccf;
+}
+.searchres .person {
+    background-color: #ddf;
+    color: inherit;
+}
+.searchres .person td {
+    border-top: 2px solid black;
+}
+.searchres .select {
+    width: 1em;
+    vertical-align: middle;
+    padding: 0 1ex 0 1ex;
+}
+.searchres .case td {
+    background-color: #fde;
+    border-top: 1px solid #bbb;
+}
+.searchres .deletedcase td {
+    background-color: #fde;
+    border-top: 1px solid #bbb;
+    color: #888;
+}
+.searchres .caseinfo {
+    padding-left: 2ex;
+}
+.searchres .buttcol {
+    text-align: right;
+    vertical-align: middle;
+    font-size: 83%;
+    width: 7.2em;
+}
+.searchres .buttcol .butt {
+    width: 100%;
+    font-size: 100%;
+}
+
+/* Task related info gets it's own styling */
+.task {
+    color: #008;
+    background-color: #eef;
+    border-left: 1ex solid #008;
+    padding-left: 1ex;
+    margin-bottom: 1ex;
+}
+.task th {
+    text-align: left;
+}
+.task td {
+    vertical-align: top;
+}
+.task .fauxrule {
+    height: 1px;
+    background-color: #bbd;
+    padding-top: 0px;
+    padding-bottom: 0px;
+}
+
+.task .fill {
+    width: 100%;
+}
+
+/* Case Contacts list */
+.casecontacts {
+    width: 100%;
+    padding-left: 1em;
+    padding-right: 1em;
+    empty-cells: show;
+    border-spacing: 0;
+}
+.casecontacts th {
+    vertical-align: top;
+    text-align: left;
+    border-bottom: 1px solid black;
+    background-color: #fd8;
+}
+.casecontacts td {
+    vertical-align: top;
+    padding-left: 0.2em;
+    padding-right: 0.2em;
+    background-color: #feb;
+}
+.casecontacts .row td {
+    border-bottom: 1px solid #ccc;
+}
+.casecontacts .select {
+    background-color: #ddf;
+}
+.casecontacts .buttcol {
+    text-align: right;
+}
+.casecontacts .page-select {
+    background-color: #ccf;
+}
+
+.choosenewcontact {
+    width: 100%;
+    empty-cells: show;
+    border-collapse: collapse;
+    padding-right: 1em;
+}
+.choosenewcontact td {
+    background-color: #eee;
+    text-align: left;
+    vertical-align: baseline;
+    border-bottom: 2px solid white;
+    padding: 2px;
+}
+.choosenewcontact .name {
+    font-weight: bold;
+}
+.choosenewcontact .buttcol {
+    text-align: right;
+}
+
+/* Unit user admin */
+.adminusers {
+    background-color: #ffe;
+    color: inherit;
+    text-align: left;
+}
+.adminusers thead th {
+    background-color: #ddb;
+    color: inherit;
+    text-align: left;
+    font-weight: bold;
+}    
+.adminusers td {
+    vertical-align: top;
+}
+.adminusers .darker {
+    background-color: #eed;
+    color: inherit;
+}
+
+/* LHS tabs, as used by the report editor */
+.ltab {
+    width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+}
+.ltab .tabcontent {
+    vertical-align: top;
+    background: #eee;
+    padding: 1ex;
+    border-left: 2px solid #888;
+}
+.ltab .tabs {
+    vertical-align: top;
+    border-right: 2px solid #888;
+    padding: 0;
+}
+.ltab .tabs input {
+    display: block;
+    position: relative;
+    font-size: 83%;
+    left: 2px;
+    height: 3em;
+    width: 100%;
+    background: #bbf;
+    border: 2px solid #888;
+    margin-bottom: 2px;
+    padding: 0;         /* kills some very strange opera behaviour */
+    font-weight: bold;
+    margin-left: auto;  /* minimises effect of buggy width calculations */
+}
+.ltab .tabs input:hover {
+    background: #dde;
+}
+.ltab .tabs .selected {
+    background: #eee;
+    border-right-color: #eee;
+}
+.ltab .tabs .act {
+    height: 2em;
+    position: relative;
+    left: -2px;
+    border: 2px solid #888;
+    background: #aaa;
+}
+.ltab .tabs .danger {
+    background: #d88;
+}
+.ltab br {
+    line-height: 1ex;
+}
+.tabcontent .gridbox {
+    border-collapse: collapse;
+}
+.tabcontent .gridbox .line {
+    background: #ddd;
+    border: solid #eee;
+    border-width: 2px 0 2px 0;
+    padding: 2px;
+}
+.tabcontent .gridbox th {
+    background: #ccc;
+    border: solid #ccc;
+    border-width: 3px 0 3px 0;
+}
+.tabcontent .gridbox .rule {
+    height: 3px;
+    background: #aaa;
+    border-top: 3px solid #eee;
+}
+
+.rfields {
+}
+.rfields th {
+    white-space: nowrap;
+    vertical-align: baseline;
+    text-align: left;
+}
+.rfields td {
+    vertical-align: baseline;
+}
+.rfields .rmw {
+    min-width: 30em;
+}
+.rfields td textarea {
+    vertical-align: text-top;
+    margin-top: -2px;
+}
+.rfields .wide {
+    width: 100%;
+}
+.rfields .wide * {
+    width: 100%;
+}
+.rfields table td {
+    vertical-align: baseline;
+}
+
+.minselect select {
+    min-width: 40em;
+}
+
+.rfilter {
+    border: 2px solid #88c;
+    margin: 1.5ex 1ex 1ex 1ex;
+    padding-top: 0.5ex;
+}
+.rfilter .rconj {
+    color: #88c;   
+    font-weight: bold;
+    font-size: 83%;
+    margin-top: -2ex;
+    height: 2.5ex;
+    margin-left: 2ex;
+    background-color: #eee;
+    position: absolute;
+    padding: 0 0.5ex;
+}
+
+.rfilter .rclause {
+    border: 2px solid #c88;
+    padding: 2px 0.5ex;
+    text-align: left;
+    background: #fee;
+    margin: 1ex;
+    position: relative;
+}
+.rfilter .rselected {
+    background: #fcc !important;
+}
+.rfilter .reditbut {
+    float: right;
+    background: #fee;
+}
+.rfilter .radd {
+    background: inherit;
+    padding: 1ex;
+}
+.rfilter .rop {
+    color: #00c;
+    text-align: left;
+    font-weight: bold;
+    font-size: 83%;
+    margin: 1ex;
+}
+.edit-filter {
+    border: 2px solid #c88;
+    padding: 2px 0.5ex;
+    text-align: left;
+    background: #fcc;
+    margin: 1ex;
+    position: relative;
+}
+.edit-filter td, .edit-filter th {
+    vertical-align: baseline;
+}
+.edit-filter td textarea, .edit-filter td select {
+    vertical-align: text-top;
+    margin-top: -1px;
+}
+
+/* top tabs, as used in the dataimport editor */
+
+.ttabs {
+    border-bottom: 2px solid #888;
+}
+.ttabs input {
+    height: 2em;
+    width: 11em;
+    border: solid #888;
+    border-width: 2px 2px 0px 2px;
+    margin-right: 0.5ex;
+    margin-left: 0.5ex;
+    padding-left: 0.5ex;
+    padding-right: 0.5ex;
+    font-weight: bold;
+    background: #bbf;
+}
+.ttabs input:hover {
+    background: #dde;
+}
+.ttabs .curr {
+    background: white;
+    border-bottom: none;
+    position: relative;
+    top: 2px;
+}
+.ttabs .initial {
+    background: #bff;
+}
+
+.imp-preview {
+    float: right; 
+    border: 1px solid black; 
+    background: #eee; 
+    text-align: center;
+    margin: 1em;
+}
+.edit-imp-field {
+}
+.edit-imp-field .eif-label {
+    font-style: italic;
+    font-size: 83%;
+    font-weight: bold;
+}
+.edit-imp-field label {
+    font-weight: bold;
+    color: #888;
+}
+
+.admin-content {
+    margin-left: auto;
+    margin-right: auto;
+}
+
+/* Main Admin screen elements */
+.admin-m {
+}
+
+.admin-m .op {
+    text-align: center;
+    width: 6em;
+    font-size: 120%;
+}
+.admin-m .ilabel {
+    width: 20ex;
+    text-align: right;
+    padding-right: 3px;
+}
+.admin-m .iopts {
+    text-align: left;
+    white-space: nowrap;
+}
+.admin-m .iopts label input {
+    padding-right: 0.2em;
+}
+.admin-m label {
+    font-size: 83%;
+}
+.admin-m .inputs * {
+    width: 60ex;
+}
+.admin-m .buttons {
+    text-align: center;
+}
+.admin-m .spacer {
+    padding: 0px;
+    margin: 0px;
+    height: 1px;
+    background-color: #fbb;
+    color: inherit;
+}
+
+/* Admin users, units, groups and forms search result */
+.gridtab {
+    background-color: #ffe;
+    text-align: left;
+    border-collapse: collapse;
+    margin: 0 1em 1ex 0;
+}
+.gridtab tbody td {
+    padding: 0 0.3em;
+    border: 1px solid #ddc;
+    vertical-align: baseline;
+}
+.gridtab .darker {
+    background-color: #eed;
+}
+.gridtab .darkest, .gridtab thead th, .gridtab thead td, 
+        .gridtab th, .gridtab tfoot th, .gridtab tfoot td {
+    border: 1px solid #bba;
+    background-color: #ddb;
+    padding: 0 0.3em;
+}
+.gridtab table td, .gridtab table th {
+    border: none;
+}
+
+/* Admin edit user, unit, group and form */
+.admin-u {
+    background-color: #ffe;
+    color: inherit;
+    text-align: left;
+    margin: 0 1em 1ex 0;
+    width: 95%;
+}
+
+.admin-u .darker {
+    background-color: #eed;
+    color: inherit;
+}
+.admin-u .highlight {
+    background-color: #fcc;
+    color: inherit;
+}
+.admin-u .darkest {
+    background-color: #ddb;
+    color: inherit;
+}
+.admin-u th {
+    background-color: #ddb;
+    color: inherit;
+    text-align: left;
+    font-weight: bold;
+}
+.admin-u td {
+    vertical-align: top;
+}
+.admin-u ul {
+    margin: 0;
+}
+.admin-u .buttons input {
+    width: 7em;
+    white-space: nowrap;
+}
+.admin-u .center {
+    text-align: center;
+}
+
+/* Admin syndromes */
+.admin-syndromes {
+    background-color: #fff5ff;
+    text-align: left;
+}
+
+.admin-syndromes .darker {
+    background-color: #fcf;
+}
+
+.admin-syndromes th {
+    background-color: #fcf;
+    text-align: left;
+    font-weight: bold;
+}
+
+.admin-syndromes label {
+    height: 100%;
+    display: block;
+    text-align: right;
+    padding-right: 1ex;
+    border-right: 2px solid #fcf;
+    background-color: #fef;
+}
+.admin-syndromes .status {
+    vertical-align: middle;
+    background-color: #fef;
+    border: 2px solid #fcf;
+    text-align: center;
+}
+
+/* Admin form editor */
+.admin-fe {
+    width: 100%;
+    border-collapse: collapse;
+    border: none;
+    text-align: left;
+}
+.admin-fe td {
+    vertical-align: top;
+}
+
+.admin-fe .nav {
+    width: 10em;
+    padding: 0;
+}
+
+.admin-fe .nav .item {
+    display: block;
+    width: 100%;
+    white-space: nowrap;
+}
+
+.formedit {
+    width: 100%;
+    border-collapse: collapse;
+}
+.formedit td {
+    vertical-align: baseline;
+    border: none;
+}
+.formedit .container {
+    font-weight: bold;
+    background-color: #eee;
+}
+.formedit .selecting, .formedit .selecting * {
+    background-color: #fea !important;
+}
+.admin-fe-work {
+    border-collapse: collapse;
+    vertical-align: top;
+    text-align: left;
+    padding: 0;
+    width: 100%;
+    z-index: 1;
+}
+.admin-fe-work td {
+    vertical-align: top;
+}
+.admin-fe-work .section {
+    background-color: #fee;
+    font-weight: bold;
+}
+.admin-fe-work .disabled {
+    background-color: #bbb;
+}
+.admin-fe-work .number {
+    width: 2.5em;
+    font-weight: bold;
+    vertical-align: top;
+}
+.admin-fe-work .question {
+}
+.admin-fe-work .qtext {
+    vertical-align: top;
+}
+.admin-fe-work textarea {
+    width: 98%;
+}
+.admin-fe-work .inputs {
+    vertical-align: top;
+}
+.admin-fe-work .prompt {
+    white-space: nowrap;
+    vertical-align: top;
+}
+
+.admin-fe-work .line {
+    background: url("/{{APPNAME}}/images/plus-line.png") left center;
+    height: 13px;
+    white-space: nowrap;
+    empty-cells: show;
+}
+.admin-fe-work .choice {
+    background-color: #fbb;
+}
+.admin-fe-work .active {
+    background-color: #ffe8e8;
+}
+.admin-fe-work .fe-left {
+    vertical-align: top;
+    width: 49%;
+}
+.admin-fe-work .fe-divide {
+    width: 1%;
+    empty-cells: show;
+}
+.admin-fe-work .fe-right {
+    vertical-align: top;
+    width: 50%;
+}
+.admin-fe-work .pretext, .admin-fe-work .posttext {
+    font-size: 83%;
+}
+.admin-fe-work .skiptext {
+    font-size: 83%;
+    font-weight: bold;
+    color: #008;
+}
+
+.admin-fe-work .preview {
+    background-color: #fcc;
+}
+
+.f-deploy {
+    width: 90%;
+    margin-left: auto;
+    margin-right: auto;
+    background-color: #bbb;
+    vertical-align: top;
+    padding: 0;
+}
+.f-deploy th {
+    text-align: center;
+}
+.f-deploy .op {
+    text-align: center;
+    font-weight: bold;
+}
+.f-deploy .add {
+    background-color: #beb;
+}
+.f-deploy .drop {
+    background-color: #ebb;
+}
+.f-deploy .nochange {
+    background-color: #eee;
+}
+.f-deploy .typechange {
+    background-color: #eeb;
+}
+.f-deploy .warning {
+    background-color: #fea;
+}
+.f-deploy .incompatible {
+    background-color: #fc0;
+}
+.f-deploy .unknown {
+    background-color: #ebe;
+}
+
+.dupestat {
+    border-collapse: collapse;
+}
+.dupestat td {
+    border: 0px;
+    border-top: 2px solid #ffa;
+    border-bottom: 2px solid #ffa;
+    padding-left: 1ex;
+    padding-right: 1ex;
+    background-color: #ffc;
+}
+
+.dupecfg {
+    border-collapse: collapse;
+}
+.dupecfg td, .dupecfg th {
+    border: 0px;
+    border-top: 2px solid #aaf;
+    border-bottom: 2px solid #aaf;
+    padding-left: 1ex;
+    padding-right: 1ex;
+}
+.dupecfg label {
+    font-weight: bold;
+}
+.dupecfg thead th {
+    text-align: left;
+    background-color: #ccf;
+    font-weight: bold;
+}
+.dupecfg .group {
+    background-color: #ccf;
+    text-align: left;
+    vertical-align: top;
+}
+.dupecfg .disabled td {
+    background-color: #eef;
+    color: #888;
+}
+.dupecfg .button {
+    text-align: center;
+    background-color: #ccf;
+    padding: 0;
+}
+
+.crosstab {
+    text-align: center;
+    border-collapse: collapse;
+}
+.crosstab .pagehead {
+    font-size: 130%;
+    font-weight: bold;
+    text-align: left;
+    padding-top: 2ex;
+    border: none
+}
+.crosstab .colhead, .crosstab .rowhead {
+    font-size: 110%;
+}
+.crosstab th {
+    padding: 0.3ex;
+}
+.crosstab td {
+    border: 1px solid gray;
+}
+.crosstab .r {
+    border-right: 2px solid gray;
+    text-align: left;
+}
+.crosstab .b {
+    border-bottom: 2px solid gray;
+}
+.crosstab .t {
+    background-color: #ccc;
+    border-bottom: 1px solid gray;
+    border-left: 1px solid gray;
+    border-top: 2px solid gray;
+}
+.crosstab .l {
+    background-color: #ccc;
+    border-top: 2px solid gray;
+    border-left: 2px solid gray;
+    border-right: 1px solid gray;
+}
+.tagbrowse {
+    border-collapse: collapse;
+    padding-left: 1em;
+}
+.tagbrowse th {
+    background-color: #ccf;
+}
+.tagbrowse td {
+    background-color: #eef;
+    border-bottom: 1px solid #ccf;
+    padding: 0 0.4em 0 0.4em;
+}
+.tagbrowse .tag-tag {
+    font-weight: bold;
+}
+.reportbuttons {
+    position: absolute;
+    right: 1em;
+}
+.reportmenu {
+    margin: 0 2em;
+}
+.reportmenu td {
+    vertical-align: baseline;
+}
+.reportmenu .edit {
+    float: right;
+    clear: right;
+    padding-left: 1em !important;
+    font-weight: normal;
+}
+.reportmenu .rtype {
+    font-size: 83%;
+    color: #aaa;
+}
diff --git a/app/wiki.css b/app/wiki.css
new file mode 100644
index 0000000..5889a32
--- /dev/null
+++ b/app/wiki.css
@@ -0,0 +1,25 @@
+/* wiki-related markup */
+.underline { text-decoration: underline }
+ol.loweralpha { list-style-type: lower-alpha }
+ol.upperalpha { list-style-type: upper-alpha }
+ol.lowerroman { list-style-type: lower-roman }
+ol.upperroman { list-style-type: upper-roman }
+ol.arabic     { list-style-type: decimal }
+
+dl.wiki dt { font-weight: bold }
+dl.compact dt { float: left; padding-right: .5em }
+dl.compact dd { margin: 0; padding: 0 }
+
+pre.wiki, pre.literal-block {
+ background: #f7f7f7;
+ border: 1px solid #d7d7d7;
+ margin: 1em 1.75em;
+ padding: .25em;
+ overflow: auto;
+}
+table.wiki {
+ border: 2px solid #ccc;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+table.wiki td { border: 1px solid #ccc;  padding: .1em .25em; }
diff --git a/casemgr/__init__.py b/casemgr/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/casemgr/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/casemgr/addressstate.py b/casemgr/addressstate.py
new file mode 100644
index 0000000..83feac2
--- /dev/null
+++ b/casemgr/addressstate.py
@@ -0,0 +1,71 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, mergelabels, cached
+
+unknown_state = ('', 'Unknown')
+
+class EditAddressStates:
+
+    def __init__(self):
+        query = globals.db.query('address_states')
+        self.rows = query.fetchall()
+        self.sort()
+
+    def new(self, **kwargs):
+        row = self.rows.new_row(**kwargs)
+        self.rows.append(row)
+        return row
+
+    def sort(self):
+        self.rows.sort(lambda a, b: cmp(a.code, b.code))
+
+    def __len__(self):
+        return len(self.rows)
+
+    def delete(self, index):
+        del self.rows[index]
+
+    def __getitem__(self, index):
+        return self.rows[index]
+
+    def has_changed(self):
+        return self.rows.db_has_changed()
+
+    def update(self):
+        self.rows.db_update()
+
+
+class AddressStates(cached.NotifyCache):
+    notification_target = 'address_states'
+
+    def __init__(self):
+        cached.NotifyCache.__init__(self)
+        self.address_states = []
+
+    def load(self):
+        query = globals.db.query('address_states', order_by='label')
+        self.address_states[:] = query.fetchcols(('code', 'label'))
+
+    def optionexpr(self):
+        self.refresh()
+        return [unknown_state] + self.address_states
+
+
+_address_states = AddressStates()
+optionexpr = _address_states.optionexpr
diff --git a/casemgr/admin/__init__.py b/casemgr/admin/__init__.py
new file mode 100644
index 0000000..8312b3d
--- /dev/null
+++ b/casemgr/admin/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
diff --git a/casemgr/admin/dropsyndrome.py b/casemgr/admin/dropsyndrome.py
new file mode 100644
index 0000000..555f4b3
--- /dev/null
+++ b/casemgr/admin/dropsyndrome.py
@@ -0,0 +1,104 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Old systems may have form instance tables without an ON DELETE CASCADE
+# constraint on the summary_id foreign key. These can be identified with:
+#
+#   SELECT l.relname 
+#       FROM pg_constraint 
+#       JOIN pg_class AS l ON (l.oid = conrelid)
+#       JOIN pg_class AS f ON (f.oid = confrelid)
+#       WHERE l.relname ~ 'form_.*_[0-9]{5}' and confdeltype='a';
+#
+
+import config
+from casemgr import globals
+
+class DropSyndromeError(globals.Error): pass
+
+Error = DropSyndromeError
+
+class ClearSyndrome:
+    def __init__(self, syndrome_id):
+        self.syndrome_id = syndrome_id
+        self.case_count = None
+        self.form_count = None
+        self.task_count = None
+        self.errors = []
+        self.acknowledge = None
+
+    def update_counts(self):
+        self.errors = []
+
+        # XXX CONTACT - warn about contacts here?
+
+        query = globals.db.query('cases')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        self.case_count = query.aggregate('count(*)')
+
+        query = globals.db.query('case_form_summary')
+        query.join('JOIN cases USING (case_id)')
+        query.where('cases.syndrome_id = %s', self.syndrome_id)
+        self.form_count = query.aggregate('count(*)')
+
+        query = globals.db.query('tasks')
+        query.join('JOIN cases USING (case_id)')
+        query.where('cases.syndrome_id = %s', self.syndrome_id)
+        self.task_count = query.aggregate('count(*)')
+
+    def delete(self, last_case_count, last_form_count):
+        if self.acknowledge != 'ACK':
+            raise Error('Warning not acknowledged!')
+        self.update_counts()
+        if (self.case_count != last_case_count or 
+            self.form_count != last_form_count):
+            raise Error('Could not delete %s - case or form count changed!' %
+                        config.syndrome_label)
+        query = globals.db.query('tasks')
+        sub = query.in_select('case_id', 'cases')
+        sub.where('syndrome_id = %s', self.syndrome_id)
+        query.delete()
+
+        query = globals.db.query('cases')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        query.delete()
+
+        query = globals.db.query('persons')
+        sub = query.in_select('person_id', 'persons')
+        sub.join('LEFT JOIN cases USING (person_id)')
+        sub.where('case_id IS null')
+        query.delete()
+
+
+class DropSyndrome(ClearSyndrome):
+    def update_counts(self):
+        ClearSyndrome.update_counts(self)
+
+        query = globals.db.query('syndrome_types', for_update=True)
+        query.where('syndrome_id = %s', self.syndrome_id)
+        syndrome = query.fetchone()
+        if not syndrome:
+            raise Error('%s not found' % (config.syndrome_label))
+        if syndrome.enabled:
+            self.errors.append('%s is still enabled' % (config.syndrome_label))
+
+    def delete(self, last_case_count, last_form_count):
+        ClearSyndrome.delete(self, last_case_count, last_form_count)
+        query = globals.db.query('syndrome_types')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        query.delete()
diff --git a/casemgr/admin/formedit.py b/casemgr/admin/formedit.py
new file mode 100644
index 0000000..d777676
--- /dev/null
+++ b/casemgr/admin/formedit.py
@@ -0,0 +1,385 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard lib
+try:
+    set
+except NameError:
+    from sets import Set as set
+import sys
+
+# App modules
+from cocklebur import form_ui, dbobj
+from casemgr.admin.questionedit import _boolfix, QuestionEdit, QuestionNameError
+
+class QuestionEditInfo:
+
+    def __init__(self, root, container, question, insert_index=None):
+        self.root = root
+        self.container = container
+        self.question = question
+        self.element = None
+        if question is not None:
+            self.element = question.element
+        self.insert_index = insert_index
+        self.other_column_names = set()
+        self.other_condition_names = set()
+        self.section_title = str(container.text)
+        self.condition_renames = {}
+        if len(self.section_title) > 30:
+            self.section_title = self.section_title[:30] + '...'
+
+    def sorted_condition_names(self):
+        names = list(self.other_condition_names)
+        names.sort()
+        return names
+
+    def reset_question_names(self):
+        self.question_column_names = set()
+        self.question_condition_names = set()
+
+    def check_column_name(self, name):
+        if name in self.other_column_names:
+            raise QuestionNameError('field name %r already used by another '
+                                    'question' % name)
+        elif name in self.question_column_names:
+            raise QuestionNameError('field name %r used more than once in '
+                                    'this question' % name)
+        try:
+            dbobj.valid_identifier(name)
+        except dbobj.DatabaseError, e:
+            raise QuestionNameError('field name: %s' % e)
+        self.question_column_names.add(name)
+
+    def check_condition_name(self, name):
+        if name in self.other_condition_names:
+            raise QuestionNameError('condition name %r already used by another '
+                                    'question' % name)
+        elif name in self.question_condition_names:
+            raise QuestionNameError('condition name %r used more than once in '
+                                    'this question' % name)
+        self.question_condition_names.add(name)
+
+    def condition_rename(self, old, new):
+        self.condition_renames[old] = new
+
+    def _question_rename_conditions(self, question):
+        question.element.triggers = [self.condition_renames.get(n, n)
+                                     for n in question.element.triggers]
+
+    def commit(self, element):
+        if self.insert_index is not None:
+            self.question = Question(element)
+            self.container.insert(self.insert_index, self.question)
+        else:
+            self.question.set_element(element)
+            self.question.element.label = self.question.get_label()
+        self.question.changed = True
+        self.root.walk_questions(self._question_rename_conditions)
+        self.root.update_xlinks()
+        return self.question
+
+
+class SectionEdit:
+
+    def __init__(self, parent, section=None, insert_index=None):
+        self.parent = parent
+        self.insert_index = insert_index
+        if section is None:
+            section = Container()
+            self.parent.insert(insert_index, section)
+        self.node = section
+        self.text = self.node.text
+
+    def commit(self):
+        self.node.text = self.text
+
+    def rollback(self):
+        if self.insert_index is not None:
+            self.parent.remove(self.node)
+
+
+class Node(object):
+
+    node_type = None
+
+    def __init__(self):
+        pass
+
+    def clear_changed(self):
+        pass
+
+    def has_changed(self):
+        return False
+
+    def update_path(self, path=None):
+        if path is not None:
+            self.path = path
+
+    def id(self):
+        return self.node_type[0].upper() + self.path
+
+    def get_label(self):
+        # quick and dirty hack to transform path into a human readable label
+        path = [str(int(pc)+1) for pc in self.path.split('_') if pc]
+        return '.'.join(path)
+
+    def to_form(self):
+        pass
+
+    def collect_edit_info(self, edit_info):
+        pass
+
+    def walk_questions(self, fn):
+        pass
+
+    def css_class(self):
+        styles = [self.node_type, 'selectable']
+        if getattr(self, 'disabled', False):
+            styles.append('disabled')
+        return ' '.join(styles)
+
+
+class Question(Node):
+
+    node_type = 'question'
+
+    def __init__(self, element):
+        Node.__init__(self)
+        if element is not None:
+            self.set_element(element)
+
+    def set_element(self, element):
+        self.element = element
+        self.text = element.text
+        self.help = element.help
+        self.inputs = element.inputs
+        self.disabled = element.disabled
+
+    def update_path(self, path=None):
+        Node.update_path(self, path)
+        self.element.label = self.get_label()
+
+    def skiptext(self):
+        return self.element.skiptext()
+
+    def clear_changed(self):
+        self.changed = False
+
+    def has_changed(self):
+        return self.changed
+
+    def to_form(self):
+        return self.element    
+
+    def collect_edit_info(self, edit_info):
+        for input in self.element.inputs:
+            edit_info.other_column_names.update(input.get_column_names())
+            # Ultimately, this will be the skip name, not the column name, and
+            # an input will potentially have more than on skip.
+            for skip in input.skips:
+                edit_info.other_condition_names.add(skip.name)
+
+    def walk_questions(self, fn):
+        fn(self)
+
+
+class EndContainer(Node):
+
+    node_type = 'end'
+
+
+class Container(Node):
+
+    node_type = 'section'
+
+    def __init__(self, container_element=None, text=''):
+        Node.__init__(self)
+        self.path = None
+        self.children = []
+        self.text = text
+        if container_element is not None:
+            self.text = container_element.text
+            for element in container_element.children:
+                if hasattr(element, 'children'):
+                    node = Container(element)
+                else:
+                    node = Question(element)
+                self.children.append(node)
+        self.children.append(EndContainer())
+
+    def has_changed(self):
+        if self.initial_text != self.text or self.changed:
+            return True
+        for child in self.children:
+            if child.has_changed():
+                return True
+        return False
+
+    def clear_changed(self):
+        self.initial_text = self.text
+        self.changed = False
+        for child in self.children:
+            child.clear_changed()
+
+    def to_form(self, cls=form_ui.Section):
+        section = cls(self.text)
+        for content in self.children:
+            child = content.to_form()
+            if child is not None:
+                section.append(child)
+        return section
+
+    def update_path(self, path=None):
+        Node.update_path(self, path)
+        for i, child in enumerate(self.children):
+            if self.path:
+                path = '%s_%d' % (self.path, i)
+            else:
+                path = '%d' % i
+            child.update_path(path)
+
+    def remove(self, child):
+        i = self.children.index(child)
+        del self.children[i]
+        self.changed = True
+        self.update_path()
+
+    def replace(self, index, node):
+        self.children[index] = node
+        self.changed = True
+        self.update_path()
+
+    def insert(self, index, node):
+        self.children.insert(index, node)
+        self.changed = True
+        self.update_path()
+
+    def collect_edit_info(self, edit_info):
+        for child in self.children:
+            if child is not edit_info.question:
+                child.collect_edit_info(edit_info)
+
+    def walk_questions(self, fn):
+        for child in self.children:
+            child.walk_questions(fn)
+
+
+class Root(Container):
+
+    form_types = 'case', 'contact'
+
+    def __init__(self, root):
+        Container.__init__(self, root) 
+        self.allow_multiple = root.allow_multiple
+        self.form_type = root.form_type
+        self.update_time = root.update_time
+        self.update_path('')
+        self.update_xlinks()    # Not strictly necessary, but good for testing
+        self.clear_changed()
+        self.changed = True
+
+    def clear_changed(self):
+        Container.clear_changed(self)
+        self.changed = False
+        self.allow_multiple_orig = _boolfix(self.allow_multiple)
+        self.form_type_orig = self.form_type
+
+    def has_changed(self):
+#        print >> sys.stderr, repr(self.allow_multiple_orig), repr(_boolfix(self.allow_multiple)), repr(self.form_type_orig), repr(self.form_type)
+        if (self.allow_multiple_orig != _boolfix(self.allow_multiple) or
+            self.form_type_orig != self.form_type or self.changed):
+            return True
+        return Container.has_changed(self)
+
+    def to_form(self, name=None):
+        form = Container.to_form(self, cls=form_ui.Form)
+        if _boolfix(self.allow_multiple):
+            form.allow_multiple = True
+        if self.form_type:
+            form.form_type = self.form_type
+        if name:
+            form.name = name
+        return form
+
+    def find_node(self, path):
+        path = path[1:]
+        if path:
+            nodes = [self]
+            for index in path.split('_'):
+                nodes.append(nodes[-1].children[int(index)])
+            return nodes[-2:]
+        else:
+            return None, self
+
+    def update_xlinks(self):
+        helper = form_ui.XlinkHelper()
+        self.walk_questions(lambda q: q.element.update_xlinks(helper))
+
+    def new_question(self, path):
+        parent, insert_node = self.find_node(path)
+        insert_index = parent.children.index(insert_node)
+        edit_info = QuestionEditInfo(self, parent, None, insert_index)
+        self.collect_edit_info(edit_info)
+        return QuestionEdit(edit_info)
+
+    def edit_question(self, path):
+        parent, question = self.find_node(path)
+        edit_info = QuestionEditInfo(self, parent, question)
+        self.collect_edit_info(edit_info)
+        return QuestionEdit(edit_info)
+
+    def new_section(self, path):
+        parent, insert_node = self.find_node(path)
+        insert_index = parent.children.index(insert_node)
+        return SectionEdit(parent, None, insert_index)
+
+    def edit_section(self, path):
+        parent, section = self.find_node(path)
+        return SectionEdit(parent, section)
+
+    def copy(self, paths):
+        if paths == 'S':
+            cut_buffer = self.to_form()
+        else:
+            cut_buffer = form_ui.Form(None)
+            for pathname in paths.split(','):
+                parent, node = self.find_node(pathname)
+                formnode = node.to_form()
+                if formnode is not None:
+                    cut_buffer.append(formnode)
+        return cut_buffer
+
+    def cut(self, paths):
+        cut_buffer = self.copy(paths)
+        if paths == 'S':
+            del self.children[:-1]
+            self.changed = True
+        else:
+            for pathname in paths.split(',')[::-1]:
+                parent, node = self.find_node(pathname)
+                parent.remove(node)
+        return cut_buffer
+
+    def paste(self, pathname, cut_buffer):
+        fragment = Container(cut_buffer)
+        parent, insert_before = self.find_node(pathname)
+        insert_index = parent.children.index(insert_before)
+        parent.children[insert_index:insert_index] = fragment.children[:-1]
+        parent.changed = True
+        parent.update_path()
diff --git a/casemgr/admin/formmeta.py b/casemgr/admin/formmeta.py
new file mode 100644
index 0000000..10a3957
--- /dev/null
+++ b/casemgr/admin/formmeta.py
@@ -0,0 +1,246 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard modules
+import os
+import re
+import pwd
+import time
+import errno
+
+# Project modules
+from casemgr import globals
+from cocklebur import datetime, form_ui, dbobj
+from casemgr.admin import formedit
+from casemgr.admin.formrollforward import formrollforward
+from casemgr.formutils import deploy, delete, rename, usedby
+import config
+
+class Error(Exception): pass
+
+Errors = (form_ui.FormError, Error, dbobj.DatabaseError)
+
+# The form label may potentially be used as a filename, which has security
+# implications if not carefully validated.
+valid_identifier_re = re.compile('^[a-z][a-z0-9_]*$', re.IGNORECASE)
+def valid_form_label(label):
+    dbobj.valid_identifier(label, 'form name', reserve=len('form__00000_pkey'))
+    if not label.islower():
+        raise dbobj.IdentifierError('%r must be all lower case' % label)
+    if not valid_identifier_re.match(label):
+        raise dbobj.IdentifierError('form name %r contains invalid characters'% 
+                                    label)
+
+
+class FormMeta:
+    orig_label = '[orig]'
+
+    def __init__(self, dbrow, cred=None):
+        self.name = dbrow.label         # Yes, it's muddled-up
+        self.cred = cred
+        self.root = None
+        self.version = None
+        self._set_dbrow(dbrow)
+        try:
+            self.load_version(self.highest_file_vers())
+        except form_ui.NoFormError:
+            self.from_form(form_ui.Form(''))
+            self.clear_changed()
+
+    def from_form(self, form):
+        self.root = formedit.Root(form)
+        if not self.name:
+            self.name = form.name
+
+    def to_form(self):
+        return self.root.to_form(self.name)
+
+    def copy_attrs(self, src, dst):
+        for attr in ('allow_multiple', 'form_type'):
+            setattr(dst, attr, getattr(src, attr))
+
+    def _set_dbrow(self, dbrow):
+        self.dbrow = dbobj.RowCache(dbrow)
+
+    def vers_deployed(self):
+        """
+        Return the currently deployed version number
+        """
+        return self.dbrow.cur_version
+
+    def _vers_from_str(self, v):
+        if not v or v == self.orig_label:
+            return None
+        else:
+            return int(v)
+
+    def vers_changed(self, v):
+        """
+        Compare v to the currently loaded version.
+
+        v can be a string (from form input), in which case
+        it is cast to an int.
+        """
+        return self._vers_from_str(v) != self.version
+
+    def clear_changed(self):
+        """
+        Clear "changed" status.
+        """
+        self.root.clear_changed()
+
+    def has_changed(self):
+        """
+        Has anything changed?
+        """
+        return self.root.has_changed()
+
+    def available_vers(self):
+        return globals.formlib.versions(self.name)
+
+    def highest_file_vers(self, name=None, path=None):
+        if name is None:
+            name = self.name
+        return globals.formlib.latest_version(name)
+
+    def load_version(self, version):
+        if isinstance(version, basestring):
+            version = self._vers_from_str(version)
+        form = globals.formlib.load(self.name, version)
+        self.version = version
+        self.from_form(form)
+        self.clear_changed()
+
+    def _write_version(self, name, *args):
+        form = self.to_form()
+        if self.cred:
+            form.author = self.cred.user.fullname
+            form.username = self.cred.user.username
+        form.update_time = datetime.now()
+        globals.formlib.save(form, name, *args)
+        self.root.update_time = form.update_time
+        return form.version, form.update_time
+
+    def _updaterow(self, db, action, *args, **kwargs):
+        """
+        Obtain a row-locked copy of the current row, and pass it
+        to the supplied callback for processing. If the callback
+        returns, the row will be committed. If it raises an
+        exception, the transaction will be rolled back.
+        """
+        query = db.query('forms', for_update=True)
+        query.where('label = %s', self.name)
+        try:
+            dbrow = query.fetchone()
+            if dbrow is None:
+                dbrow = db.new_row('forms')
+            action(dbrow, *args, **kwargs)
+        except:
+            db.rollback()
+            raise
+        db.commit()
+        self._set_dbrow(dbrow)
+
+    def save(self, db):
+        """
+        Save the current form definition to the next available
+        version number.
+        """
+        def write_update_row(dbrow):
+            self.version, dbrow.def_update_time = self._write_version(self.name)
+            dbrow.db_update()
+
+        if self.dbrow.is_new():
+            self.save_as(db, self.name)
+        else:
+            self._updaterow(db, write_update_row)
+            self.clear_changed()
+
+    def deploy(self, db, rollforward_map=None):
+        """
+        Deploy the current form version (create any form instance table).
+        """
+        def update_row(dbrow):
+            highest_version = self.highest_file_vers()
+            if self.version != highest_version:
+                # If not the latest version, save first, so "deployed" version
+                # always increments.
+                version, update_time = self._write_version(self.name)
+                self.version = version
+            table = globals.formlib.tablename(self.name, self.version)
+            form = self.to_form()
+            deploy.make_form_table(db, form, table)
+            if config.form_rollforward:
+                assert rollforward_map is not None
+                try:
+                    formrollforward(db, self.name, self.version, 
+                                    rollforward_map)
+                except dbobj.DatabaseError, e:
+                    raise Error('Data roll-forward (and deploy) failed - new schema may be incompatible (%s)' % e)
+            dbrow.cur_version = self.version
+            self.copy_attrs(self.root, dbrow)
+            dbrow.name = self.root.text
+            dbrow.db_update()
+            db.save_describer()
+
+        assert not self.has_changed()
+        self._updaterow(db, update_row)
+
+    def rename(self, db, new_name):
+        """
+        Rename the current form
+        """
+        assert not self.has_changed()
+        new_name = new_name.lower()
+        valid_form_label(new_name)
+        try:
+            self.dbrow = rename.rename_form(self.name, new_name)
+            self.name = self.dbrow.label
+        except dbobj.DuplicateKeyError, e:
+            raise Error('New name is already used')
+
+    def save_as(self, db, new_name):
+        """
+        Save the current form with a new name (creating forms table
+        row and form definition file).
+        """
+        new_name = new_name.lower()
+        valid_form_label(new_name)
+        dbrow = db.new_row('forms')
+        version, dbrow.def_update_time = self._write_version(new_name)
+        dbrow.label = new_name
+        dbrow.name = self.root.text
+        try:
+            dbrow.db_update()
+        except dbobj.DuplicateKeyError:
+            db.rollback()
+            raise Error('New name is already used')
+        db.commit()
+        self.name = new_name
+        self.version = version
+        self._set_dbrow(dbrow)
+        self.clear_changed()
+
+    def used_by(self, db):
+        """
+        Return an ordered list of syndromes that use this form
+        """
+        return usedby.form_syndromes(self.name)
+
+    def delete(self, db):
+        self.dbrow = delete.delete_form(self.name)
diff --git a/casemgr/admin/formrollforward.py b/casemgr/admin/formrollforward.py
new file mode 100644
index 0000000..1a7e3ca
--- /dev/null
+++ b/casemgr/admin/formrollforward.py
@@ -0,0 +1,68 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj, form_ui
+from casemgr import globals
+
+def formrollforward(db, name, target_vers, rollforward_map):
+    """
+    This function makes no attempt to update summary text, as this
+    would require loading all the form instance data!
+
+    We also can't cope with the source data being spread across
+    multiple tables (as is the case when the configuration option
+    form_rollforward has been set to false) - we'd need a rollforward
+    map for each previous version of the form, and the chances of
+    a successful roll-forward would be greatly diminished.
+    """
+    db.lock_table('case_form_summary', 'EXCLUSIVE')
+    query = db.query('case_form_summary')
+    query.where('form_label = %s', name)
+    summ_id_by_version = {}
+    for summary in query.fetchall():
+        try:
+            ids = summ_id_by_version[summary.form_version]
+        except KeyError:
+            ids = summ_id_by_version[summary.form_version] = []
+        ids.append(summary.summary_id)
+    if not summ_id_by_version:
+        return                    # Nothing needing roll-forward
+    if len(summ_id_by_version) > 1:
+        raise dbobj.DatabaseError('Cannot roll-forward form data - system '
+                                  'has been operated with form_rollforward '
+                                  'set to False, and form data is spread over '
+                                  'multiple tables.')
+    from_vers = summ_id_by_version.keys()[0]
+    src_cols, dst_cols = zip(*rollforward_map)
+    sys_cols = ('summary_id', 'form_date')
+    src_cols = ','.join(sys_cols + src_cols)
+    dst_cols = ','.join(sys_cols + dst_cols)
+    src_table = globals.formlib.tablename(name, from_vers)
+    dst_table = globals.formlib.tablename(name, target_vers)
+    curs = db.cursor()
+    try:
+        dbobj.execute(curs, 'UPDATE case_form_summary SET form_version=%s '
+                            'WHERE form_label = %s', (target_vers, name))
+        dbobj.execute(curs, 'INSERT INTO %s (%s) SELECT %s FROM %s' %\
+                        (dst_table, dst_cols, src_cols, src_table))
+    finally:
+        curs.close()
diff --git a/casemgr/admin/questionedit.py b/casemgr/admin/questionedit.py
new file mode 100644
index 0000000..4477636
--- /dev/null
+++ b/casemgr/admin/questionedit.py
@@ -0,0 +1,540 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+import inspect
+try:
+    set
+except NameError:
+    from sets import Set as set
+from cocklebur import form_ui, dbobj
+from cocklebur import introspect
+from cocklebur.tuplestruct import TupleStruct
+from cocklebur.safehtml import safehtml
+
+
+class QuestionNameError(Exception): pass
+
+def _boolfix(v):
+    return str(v) == 'True'
+
+class Choice(TupleStruct):
+    __slots__ = 'key', 'label'
+
+class Default:
+    def __init__(self):
+        self.type = 'None'
+        self.value = ''
+        self.input_type = None
+
+    def to_form(self, kwargs):
+        if self.type == 'value':
+            kwargs['default'] = self.value
+        elif self.type == 'None':
+            pass
+        else:
+            kwargs['default'] = self.type
+
+    def init_value(self, value):
+        if value is not None:
+            for tag, label in self.get_options():
+                if tag == value:
+                    self.type = tag
+                    break
+            else:
+                self.type = 'value'
+                self.value = value
+
+    def set_input_type(self, input_type):
+        self.input_type = input_type
+
+    def get_options(self):
+        cls = input_classes.get(self.input_type, None)
+        if cls is None:
+            return []
+        else:
+            return cls.default_options
+
+
+class _FS(TupleStruct):
+    __slots__ = 'label', 'name', 'pytype', 'has_meth'
+    def has(self, inst):
+        return not self.has_meth or getattr(inst, self.has_meth)()
+
+class Skip(object):
+    def __init__(self, skip=None):
+        self.not_selected = ''
+        self.show_msg = 'True'
+        self.skip_remaining = 'True'
+        if skip is None:
+            self.initial_name = self.name = ''
+            self.values = []
+        else:
+            self.initial_name = self.name = skip.name
+            if skip.not_selected:
+                self.not_selected = 'True'
+            if not skip.show_msg:
+                self.show_msg = ''
+            if not skip.skip_remaining:
+                self.skip_remaining = ''
+            self.values = skip.values
+
+    def to_form(self):
+        return form_ui.Skip(self.name, self.values, 
+                            self.not_selected == 'True',
+                            self.show_msg == 'True',
+                            self.skip_remaining == 'True')
+
+class Input(object):
+
+    fields = [
+        _FS('Pre-text', 'pre_text', str, 'has_pre_text'),
+        _FS('Post-text', 'post_text', str, 'has_post_text'),
+        _FS('Maximum Size', 'maxsize', int, 'has_maxsize'),
+        _FS('Minimum Value', 'minimum', float, 'has_minmax'),
+        _FS('Maximum Value', 'maximum', float, 'has_minmax'),
+    ]
+    unknown_seq = 0
+    with_time = False
+
+    def __init__(self, element, edit_info):
+        self.edit_info = edit_info
+        self.column = ''
+        self.input_type = self.want_type = 'None'
+        self.required = 'False'
+        self.summarise = 'False'
+        self.edit_choices = []
+        self.direction = 'auto'
+        self.choice_order = ''
+        self.input_groups = input_groups
+        self.default = Default()
+        self.skips = []
+        self.clear_err()
+        attrs = (
+            'pre_text', 'post_text', 'label', 'maxsize',
+            'minimum', 'maximum'
+        )
+        if element:
+            self.column = element.column
+            self.set_type(element.__class__.__name__)
+            if hasattr(element, 'default'):
+                self.default.init_value(element.default)
+            if self.has_direction():
+                self.direction = element.direction
+            if hasattr(element, 'choices'):
+                self.edit_choices = [Choice(name, label) 
+                                     for name, label in element.choices]
+                self.skips = [Skip(skip) for skip in element.skips]
+            if element.required:
+                self.required = 'True'
+            if element.summarise:
+                self.summarise = 'True'
+            for attr in attrs:
+                setattr(self, attr, getattr(element, attr, None))
+        else:
+            for attr in attrs:
+                setattr(self, attr, '')
+
+    def get_choices(self):
+        return [(c.key, c.label) 
+                for i, c in enumerate(self.edit_choices)]
+    choices = property(get_choices)
+
+    def get_pre_text(self):
+        return self.pre_text
+
+    def get_post_text(self):
+        return self.post_text
+
+    describe_skip = form_ui.ChoicesBase.describe_skip.im_func
+    skiptext = form_ui.ChoicesBase.skiptext.im_func
+
+    def choicevalues(self):
+        return [c.key for c in self.edit_choices]
+
+    def clear_err(self):
+        self.error = ''
+
+    def get_class(self):
+        return input_classes.get(self.input_type)
+
+    def to_form(self):
+        def add_attr(attr, attr_type, value=None):
+            cls_attr = introspect.getclassattr(cls, attr)
+            if value is None:
+                value = getattr(self, attr)
+            if value == '':
+                value = None
+            if value is not None:
+                try:
+                    value = attr_type(value)
+                except (TypeError, ValueError):
+                    value = attr_type()
+            if value is not None and cls_attr != value:
+                kwargs[attr] = value
+        cls = self.get_class()
+        kwargs = {}
+        self.default.to_form(kwargs)
+        add_attr('required', bool, _boolfix(self.required))
+        add_attr('summarise', bool, _boolfix(self.summarise))
+        add_attr('label', str, self.label)
+        if self.has_direction():
+            add_attr('direction', str, self.direction)
+        for fs in self.fields:
+            if fs.has(self):
+                add_attr(fs.name, fs.pytype)
+        if self.has_choices():
+            if introspect.getclassattr(cls, 'choices') is None:
+                kwargs['choices'] = self.choices
+            if self.skips:
+                kwargs['skips'] = [skip.to_form() for skip in self.skips]
+        return cls(self.column, **kwargs)
+
+    def commit(self):
+        for skip in self.skips:
+            if skip.initial_name and skip.initial_name != skip.name:
+                self.edit_info.condition_rename(skip.initial_name, skip.name)
+
+    def render_horizontal(self):
+        if self.has_direction():
+            if self.direction == 'auto':
+                # Logic copied from cocklebur.form_ui.inputbase
+                fieldlens = [len(c.label) + 2 
+                            for c in self.edit_choices
+                            if c.label]
+                return sum(fieldlens) < 45;
+            else:
+                return self.direction == 'horizontal'
+        return False
+
+    def check(self):
+        if self.input_type not in input_classes:
+            return 'select an input type'
+        if not self.column or not self.column.strip():
+            return 'a column name is required'
+        # XXX for checkbox inputs, we need to iterate over the possible names
+        try:
+            self.edit_info.check_column_name(self.column)
+        except QuestionNameError, e:
+            return str(e)
+        locked_column = self.locked_column()
+        if locked_column:
+            self.column = locked_column
+        if self.column == 'form_date' and not self.is_date():
+            return 'form_date must be a Date/Time Input'
+        if self.has_choices():
+            badch_re = re.compile('[^a-z0-9_]', re.IGNORECASE)
+            for choice in self.edit_choices:
+                # A null key is used for the "not answered" case? AM 070327
+                if choice.key is None:
+                    choice.key = ''
+                else:
+                    choice.key = badch_re.sub('_', choice.key)
+            for skip in self.skips:
+                if not skip.name:
+                    return 'skips must have a name'
+                if not skip.values:
+                    return 'skips must have values selected'
+                try:
+                    self.edit_info.check_condition_name(skip.name)
+                except QuestionNameError, e:
+                    return str(e)
+        for fs in self.fields:
+            if fs.has(self):
+                value = getattr(self, fs.name, None)
+                if value is not None and value is not '':
+                    try:
+                        setattr(self, fs.name, fs.pytype(value))
+                    except (TypeError, ValueError), e:
+                        return '%s: %s' % (fs.label, e)
+        if self.has_maxsize() and self.maxsize and self.maxsize <= 0:
+            return 'Maximum size must be greater than zero'
+        try:
+            self.to_form()
+        except form_ui.FormError, e:
+            return str(e)
+        return ''
+
+    def get_column_name(self):
+        name = self.column
+        try:
+            dbobj.valid_identifier(self.column)
+        except dbobj.DatabaseError:
+            name = None
+        if not name:
+            name = 'unknown_%d' % self.unknown_seq
+            Input.unknown_seq += 1
+        return name
+
+    def format(self):
+        return ''
+
+    def get_renderer(self):
+        return getattr(self.get_class(), 'render', '')
+
+    def locked_column(self):
+        return getattr(self.get_class(), 'locked_column', '')
+
+    def has_pre_text(self):
+        return True
+#        return self.get_renderer() in ('TextInput',)
+
+    def has_post_text(self):
+        return True
+#        return self.get_renderer() in ('TextInput',)
+
+    def has_choices(self):
+        return self.get_renderer() in ('RadioList', 'DropList', 'CheckBoxes')
+
+    def has_direction(self):
+        return self.get_renderer() in ('RadioList', 'CheckBoxes')
+
+    def has_maxsize(self):
+        return self.get_renderer() in ('TextInput', 'TextArea', 'DropList', 'RadioList')
+
+    def is_numeric(self):
+        cls = self.get_class()
+        return cls is not None and issubclass(cls, form_ui.NumberInputBase)
+
+    def is_date(self):
+        return self.input_type in ('DateInput', 'DatetimeInput', 'FormDateInput')
+
+    def has_minmax(self):
+        return self.is_numeric()
+
+    def set_type(self, input_type=None):
+        if input_type is None:
+            input_type = self.want_type
+        if input_type == self.input_type:
+            return
+        self.want_type = self.input_type = input_type
+        cls = self.get_class()
+        if cls is not None:
+            cls_choices = introspect.getclassattr(cls, 'choices')
+            if cls_choices is not None:
+                self.edit_choices = [Choice(name, label) 
+                                     for name, label in cls_choices]
+            for fs in self.fields:
+                if fs.has(self):
+                    value = introspect.getclassattr(cls, fs.name)
+                    if value is not None:
+                        setattr(self, fs.name, value)
+        self.default.set_input_type(self.input_type)
+
+    def apply_choice_order(self):
+        if self.choice_order:
+            new_choices = []
+            for index in self.choice_order.split(','):
+                new_choices.append(self.edit_choices[int(index)])
+            self.edit_choices = new_choices
+
+    def page_process(self):
+        self.set_type()
+        self.apply_choice_order()
+
+    def instance_choice_fixup(self):
+        # An attempt to mutate an input type with immutable choices has been
+        # made - search parent classes for a more appropriate input type.
+        for cls in inspect.getmro(self.get_class()):
+            if not hasattr(cls, 'choices'):
+                self.set_type(cls.__name__)
+                break
+
+    def choice_move_up(self, i):
+        self.instance_choice_fixup()
+        if i > 0:
+            self.edit_choices[i], self.edit_choices[i-1] = \
+                self.edit_choices[i-1], self.edit_choices[i]
+
+    def choice_move_dn(self, i):
+        self.instance_choice_fixup()
+        if i < len(self.edit_choices) - 1:
+            self.edit_choices[i], self.edit_choices[i+1] = \
+                self.edit_choices[i+1], self.edit_choices[i]
+
+    def choice_add(self):
+        self.instance_choice_fixup()
+        self.edit_choices.append(Choice('',''))
+
+    def choice_del(self, i):
+        self.instance_choice_fixup()
+        del self.edit_choices[i]
+
+    def cond_add(self):
+        skip = Skip()
+        if not self.skips:
+            skip.name = self.column
+        self.skips.append(skip)
+
+    def cond_del(self, i):
+        del self.skips[i]
+
+
+
+class QuestionTrigger:
+    def __init__(self, name=''):
+        self.name = name
+
+    def to_form(self):
+        return self.name
+
+    def __str__(self):
+        return self.name
+
+
+class QuestionEdit:
+
+    node_type = 'question'
+
+    def __init__(self, edit_info):
+        self.edit_info = edit_info
+        element = self.edit_info.element
+        self.text = ''
+        self.help = ''
+        self.disabled = ''
+        self.inputs = []
+        self.triggers = []
+        self.trigger_mode = 'disable'
+        if element is not None:
+            self.text = element.text
+            self.help = element.help
+            if element.disabled:
+                self.disabled = 'True'
+            self.trigger_mode = element.trigger_mode
+            for name in element.triggers:
+                trigger = QuestionTrigger(name)
+                if trigger.name in self.edit_info.other_condition_names:
+                    self.triggers.append(trigger)
+            self.inputs = [Input(inp, self.edit_info) for inp in element.inputs]
+        self.clear_changed()
+        self.clear_err()
+
+    def clear_changed(self):
+        self.changed = False
+
+    def has_changed(self):
+        return self.changed
+
+    def input_add(self, i):
+        self.inputs.insert(i, Input(None, self.edit_info))
+        return i
+
+    def trigger_add(self):
+        self.triggers.append(QuestionTrigger())
+
+    def trigger_del(self, i):
+        del self.triggers[i]
+
+    def check(self):
+        self.edit_info.reset_question_names()
+        okay = True
+        if self.text:
+            self.text = safehtml(self.text)
+        if self.help:
+            self.help = safehtml(self.help)
+        first_error = None
+        for index, input in enumerate(self.inputs):
+            input.error = input.check()
+            if input.error and first_error is None:
+                first_error = index
+        return first_error
+
+    def clear_err(self):
+        self.error = ''
+        for input in self.inputs:
+            input.clear_err()
+
+    def to_form(self):
+        args = {}
+        args['inputs'] = [input.to_form() for input in self.inputs]
+        if self.triggers:
+            args['trigger_mode'] = self.trigger_mode
+            args['triggers'] = [trigger.to_form() 
+                                for trigger in self.triggers if trigger.name]
+        if self.help:
+            args['help'] = self.help
+        if self.disabled:
+            args['disabled'] = True
+        return form_ui.Question(self.text, **args)
+
+    def rollback(self):
+        pass
+
+    def commit(self):
+        form = self.to_form()
+        for input in self.inputs:
+            input.commit()
+        return self.edit_info.commit(form)
+
+    def op(self, target, op, index=None, subindex=None):
+        if index is not None:
+            index = int(index)
+        obj = None
+        args = ()
+        if target in ('input', 'trigger'):
+            obj = self
+            if index is not None:
+                args = (index,)
+        elif target in ('cond', 'choice'):
+            obj = self.inputs[index]
+            args = ()
+            if subindex is not None:
+                args = (int(subindex),)
+        if obj:
+            fn = getattr(obj, '%s_%s' % (target, op))
+            return fn(*args)
+
+    def describe_triggers(self):
+        triggers = [str(trigger) for trigger in self.triggers]
+        if len(triggers) == 1:
+            return '%s if %s triggers.' % (self.trigger_mode, triggers[0])
+        elif triggers:
+            return '%s if any of %s triggers.' % (self.trigger_mode, 
+                                               ', '.join(triggers))
+        else:
+            return 'none'
+
+    def copy(self, indexes):
+        return [self.inputs[i].to_form() for i in indexes]
+
+    def cut(self, indexes):
+        indexes.sort()
+        cutbuf = [self.inputs[i].to_form() for i in indexes]
+        for i in indexes[::-1]:
+            del self.inputs[i]
+        return cutbuf
+
+    def paste(self, index, cutbuf):
+        self.inputs[index:index] = [Input(inp, self.edit_info) 
+                                    for inp in cutbuf]
+
+
+def collect_inputs():
+    classes = {}
+    groups = {}
+    for input in form_ui.get_inputs():
+        classes[input.__name__] = input
+        group = groups.setdefault(input.input_group or 'Miscellaneous', [])
+        group.append((input.__name__, input.type_name))
+    for group in groups.values():
+        group.sort(lambda a, b: cmp(a[1], b[1]))
+    groups = groups.items()
+    groups.sort()
+    groups.insert(0, ('None', '(Select an input type)'))
+    return classes, groups
+
+input_classes, input_groups = collect_inputs()
diff --git a/casemgr/admin/search.py b/casemgr/admin/search.py
new file mode 100644
index 0000000..3f028c8
--- /dev/null
+++ b/casemgr/admin/search.py
@@ -0,0 +1,290 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+
+
+class Search:
+    name = None
+    browse_page = None
+    edit_page = None
+
+    def __init__(self):
+        self.reset()
+
+    def clear_error(self):
+        self.error = ''
+
+    def set_error(self, error):
+        if not self.error:
+            self.error = str(error)
+
+    def init(self):
+        pass
+
+    def reset(self):
+        self.init()
+        self.clear_error()
+
+    def search(self, prefs):
+        self.clear_error()
+        try:
+            query = self.query()
+            rows = query.fetchall()
+            if len(rows) == 0:
+                return self.set_error('No matches')
+            return rows
+        except dbobj.DatabaseError, e:
+            globals.db.rollback()
+            return self.set_error(e)
+
+    def go(self, ctx, prefs):
+        rows = self.search(prefs)
+        if rows:
+            ctx.push_page(self.browse_page, rows)
+
+    def new(self, ctx, prefs):
+        ctx.push_page(self.edit_page, None)
+
+
+class PagedSearch(Search):
+
+    def search(self, prefs):
+        self.clear_error()
+        try:
+            query = self.query()
+            search = paged_search.SortablePagedSearch(globals.db, prefs, query)
+            if not search.result_count():
+                return self.set_error(search.error or 'No matches')
+            return search
+        except dbobj.DatabaseError, e:
+            globals.db.rollback()
+            return self.set_error(e)
+
+    def go(self, ctx, prefs):
+        rows = self.search(prefs)
+        if rows:
+            if len(rows) == 1:
+                ctx.push_page(self.edit_page, *rows.pkeys[0])
+            else:
+                ctx.push_page(self.browse_page, rows)
+
+
+class UserSearch(PagedSearch):
+    name = 'user'
+    browse_page = 'admin_users'
+    edit_page = 'admin_user'
+
+    def init(self):
+        self.name = self.fullname = self.unit = self.title = self.sponsor = ''
+        self.enabled = 'enabled'
+
+    def query(self):
+        query = globals.db.query('users', order_by='users.username')
+        if self.enabled == 'enabled':
+            query.where('users.enabled')
+        elif self.enabled == 'disabled':
+            query.where('not users.enabled')
+        if self.enabled == 'deleted':
+            query.where('users.deleted')
+            if self.name and self.name[-1] not in '*%':
+                self.name += '*'
+        else:
+            query.where('not users.deleted')
+        if self.name:
+            query.where('users.username ILIKE %s', dbobj.wild(self.name))
+        if self.fullname:
+            query.where('users.fullname ILIKE %s', dbobj.wild(self.fullname))
+        if self.title:
+            query.where('users.title ILIKE %s', dbobj.wild(self.title))
+        if self.unit:
+            query.join('JOIN unit_users USING (user_id)')
+            query.join('JOIN units USING (unit_id)')
+            query.where('units.name ILIKE %s', dbobj.wild(self.unit))
+        if self.sponsor:
+            query.join('JOIN users AS su'
+                       ' ON (su.user_id = users.sponsoring_user_id)')
+            or_expr = query.sub_expr('OR')
+            or_expr.where('su.username ILIKE %s', dbobj.wild(self.sponsor))
+            or_expr.where('su.fullname ILIKE %s', dbobj.wild(self.sponsor))
+        return query
+
+
+class UnitSearch(PagedSearch):
+    name = 'unit'
+    browse_page = 'admin_units'
+    edit_page = 'admin_unit'
+
+    def init(self):
+        self.name = self.group_id = ''
+        self.enabled = 'enabled'
+
+    def query(self):
+        query = globals.db.query('units', order_by='name')
+        if self.enabled == 'enabled':
+            query.where('enabled = True')
+        elif self.enabled == 'disabled':
+            query.where('enabled = False')
+        if self.name:
+            query.where('name ILIKE %s', dbobj.wild(self.name))
+        if self.group_id:
+            query.join('JOIN unit_groups USING (unit_id)')
+            query.where('group_id = %s', int(self.group_id))
+        return query
+
+
+class GroupSearch(PagedSearch):
+    name = 'group'
+    browse_page = 'admin_groups'
+    edit_page = 'admin_group'
+
+    def init(self):
+        self.group_id = ''
+
+    def query(self):
+        return globals.db.query('groups', order_by = 'group_name')
+
+    def go(self, ctx, prefs):
+        if self.group_id:
+            ctx.push_page(self.edit_page, self.group_id)
+        else:
+            PagedSearch.go(self, ctx, prefs)
+
+
+class SyndromeSearch(PagedSearch):
+    name = 'syndrome'
+    browse_page = 'admin_syndromes'
+    edit_page = 'admin_syndrome'
+
+    def init(self):
+        self.group_id = ''
+        self.name = ''
+        self.enabled = 'enabled'
+
+    def query(self):
+        query = globals.db.query('syndrome_types', order_by='name')
+        if self.name:
+            query.where('name ILIKE %s', dbobj.wild(self.name))
+        if self.enabled == 'enabled':
+            query.where('enabled = True')
+        elif self.enabled == 'disabled':
+            query.where('enabled = False')
+        if self.group_id:
+            query.join('JOIN group_syndromes USING (syndrome_id)')
+            query.where('group_id = %s', int(self.group_id))
+        return query
+
+
+class QueueSearch(PagedSearch):
+    name = 'queue'
+    browse_page = 'admin_queues'
+    edit_page = 'admin_queue'
+
+    def init(self):
+        self.name = ''
+
+    def query(self):
+        query = globals.db.query('workqueues', order_by = 'name')
+        query.where('user_id is null AND unit_id is null')
+        if self.name:
+            name_like = dbobj.wild(self.name)
+            query.where('name ILIKE %s or description ILIKE %s', 
+                name_like, name_like)
+        return query
+
+
+class BulletinSearch(PagedSearch):
+    name = 'bulletin'
+    browse_page = 'admin_bulletins'
+    edit_page = 'admin_bulletin'
+
+    def init(self):
+        self.title = self.group_id = self.before = self.after = ''
+
+    def query(self):
+        query = globals.db.query('bulletins', order_by='post_date')
+        if self.title:
+            or_query = query.sub_expr('OR')
+            or_query.where('title ILIKE %s', dbobj.wild(self.title))
+            or_query.where('synopsis ILIKE %s', dbobj.wild(self.title))
+        if self.group_id:
+            query.join('JOIN group_bulletins USING (bulletin_id)')
+            query.where('group_id = %s', int(self.group_id))
+        if self.before:
+            query.where('post_date < %s', self.before)
+        if self.after:
+            query.where('post_date > %s', self.after)
+        return query
+
+
+class FormSearch(PagedSearch):
+    name = 'form'
+    browse_page = 'admin_forms'
+    edit_page = 'admin_form_edit'
+
+    def init(self):
+        self.syndrome_id = self.form = ''
+
+    def query(self):
+        query = globals.db.query('forms', order_by = 'label')
+        if self.form:
+            form_like = dbobj.wild(self.form)
+            query.where('forms.label ILIKE %s or forms.name ILIKE %s',
+                        form_like, form_like)
+        if self.syndrome_id:
+            query.join('JOIN syndrome_forms'
+                       ' ON (syndrome_forms.form_label = forms.label)')
+            query.where('syndrome_forms.syndrome_id = %s', int(self.syndrome_id))
+        return query
+
+
+class Searches(list):
+    searches = [
+        GroupSearch,
+        SyndromeSearch,
+        FormSearch,
+        UnitSearch,
+        UserSearch,
+        QueueSearch,
+        BulletinSearch,
+    ]
+
+    def __init__(self):
+        for cls in self.searches:
+            search = cls()
+            self.append(search)
+            setattr(self, cls.name, search)
+        self.reset()
+
+    def reset(self):
+        self.group_id = ''
+        for search in self:
+            search.reset()
+
+    def clear_errors(self):
+        for search in self:
+            search.clear_error()
+
+    def search(self, ctx, name, prefs):
+        search = getattr(self, name)
+        search.group_id = self.group_id
+        search.go(ctx, prefs)
+
+    def new(self, ctx, name, prefs):
+        search = getattr(self, name)
+        search.new(ctx, prefs)
diff --git a/casemgr/admin/tablediff.py b/casemgr/admin/tablediff.py
new file mode 100644
index 0000000..fcaf08f
--- /dev/null
+++ b/casemgr/admin/tablediff.py
@@ -0,0 +1,223 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard Python libs
+import re
+import difflib
+import itertools
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+class TableDiffError(Exception): pass
+
+typeprecmap = {
+    'int8': ('int', 8),
+    'int4': ('int', 4),
+    'int2': ('int', 2),
+    'integer': ('int', 4),
+    'float4': ('float', 4),
+    'float8': ('float', 8),
+    'double precision': ('float', 8),
+}
+
+varchar_re = re.compile(r'^varchar\s*(\(\d+\))?$')
+
+def type_prec(sqltype):
+    try:
+        return typeprecmap[sqltype]
+    except KeyError:
+        match = varchar_re.match(sqltype)
+        if match:
+            prec = match.group(1)
+            if prec:
+                return 'varchar', int(prec[1:-1])
+            else:
+                return 'varchar', None
+        else:
+            return sqltype, None
+
+implicitcast_map = {
+    'float': set(('int','text','varchar')),
+    'int': set(('float', 'text','varchar')),
+    'date': set(('timestamp','text','varchar')),
+    'timestamp': set(('date','text','varchar')),
+    'time': set(('text','varchar')),
+    'varchar': set(('text',)),
+    'text': set(('varchar',)),
+}
+lossofprec_map = {
+    'float': set(('int',)),
+    'timestamp': set(('date',)),
+}
+
+def safe_conversion(from_type, to_type):
+    def normalise(sqltype):
+        return sqltype.lower().strip()
+#        return tp.upper().split('REFERENCES')[0].strip()
+    from_type = normalise(from_type)
+    to_type = normalise(to_type)
+    if from_type == to_type:
+        return True, ''
+    from_type, from_prec = type_prec(from_type)
+    to_type, to_prec = type_prec(to_type)
+    if from_type == to_type:
+        if from_prec > to_prec:
+            return False, 'overflow'
+        return True, ''
+    acceptable_types = implicitcast_map.get(from_type)
+    if not acceptable_types or to_type not in acceptable_types:
+        return False, 'incompatible types'
+    precloss_types = lossofprec_map.get(from_type)
+    if precloss_types and to_type in precloss_types:
+        return True, 'loss of precision'
+    return True, ''
+
+class DC_Column:
+    def __init__(self, op, col_a, col_b):
+        self.op = op
+        self.col_a = col_a
+        self.col_b = col_b
+        self.okay = True
+        if self.col_a is not None:
+            self.col_a_sql = self.col_a.sql_type()
+        if self.col_b is not None:
+            self.col_b_sql = self.col_b.sql_type()
+        self.style = 'unknown'          # Shouldn't happen
+        if op == 'add':
+            self.style = 'add'
+        elif op == 'drop':
+            self.style = 'drop'
+        elif self.col_a is not None and self.col_b is not None:
+            if self.col_a_sql == self.col_b_sql:
+                self.style = 'nochange'
+            else:
+                self.style = 'typechange'
+                self.okay, msg = safe_conversion(self.col_a_sql, self.col_b_sql)
+                if msg:
+                    self.op = msg
+                    if self.okay:
+                        self.style = 'warning'
+                    else:
+                        self.style = 'incompatible'
+
+
+class DC_Columns(list):
+    def __init__(self, table_desc_a, table_desc_b):
+        self.table_desc_a = table_desc_a
+        self.table_desc_b = table_desc_b
+
+    def get_dc_col(self, op, name_a, name_b):
+        col_a = col_b = None
+        if name_a:
+            col_a = self.table_desc_a.get_column(name_a)
+        if name_b:
+            col_b = self.table_desc_b.get_column(name_b)
+        return DC_Column(op, col_a, col_b)
+
+    def col(self, op, name_a, name_b):
+        self.append(self.get_dc_col(op, name_a, name_b))
+
+    def rename(self, old, new):
+        if not old or not new:
+            raise TableDiffError('Select both a current and a new column')
+        new_cols = []
+        for dc_col in self:
+            if dc_col.col_a is not None and dc_col.col_a.name == old:
+                if dc_col.col_b is not None or dc_col.op != 'drop':
+                    raise TableDiffError('Can only merge "add" with "drop"')
+                if old == new:
+                    new_cols.append(self.get_dc_col('', old, new))
+                else:
+                    new_cols.append(self.get_dc_col('rename', old, new))
+            elif dc_col.col_b is not None and dc_col.col_b.name == new:
+                if dc_col.col_a is not None or dc_col.op != 'add':
+                    raise TableDiffError('Can only merge "add" with "drop"')
+            else:
+                new_cols.append(dc_col)
+        self[:] = new_cols
+
+    def recreate(self, old, new):
+        if not old or not new:
+            raise TableDiffError('Select both a current and a new column')
+        new_cols = []
+        for dc_col in self:
+            if dc_col.col_a is not None and dc_col.col_a.name == old:
+                if dc_col.col_b is None or dc_col.col_b.name != new:
+                    raise TableDiffError('Can only split same row')
+                new_cols.append(self.get_dc_col('drop', dc_col.col_a.name,None))
+                new_cols.append(self.get_dc_col('add', None, dc_col.col_b.name))
+            else:
+                new_cols.append(dc_col)
+        self[:] = new_cols
+
+    def okay(self):
+        for dc_col in self:
+            if not dc_col.okay:
+                return False
+        return True
+
+    def is_drop_all(self):
+        drop = 0
+        rollforward = 0
+        for dc_col in self:
+            if dc_col.col_a is not None:
+                if dc_col.col_b is None:
+                    drop += 1
+                else:
+                    rollforward += 1
+        return bool(drop and not rollforward)
+
+    def rollforward_map(self):
+        rollforward = []
+        for dc_col in self:
+            if dc_col.col_a is not None and dc_col.col_b is not None:
+                rollforward.append((dc_col.col_a.name, dc_col.col_b.name))
+        return rollforward
+
+
+def col_names(table_desc):
+    if table_desc is None:
+        return []
+    names = [c.name for c in table_desc.columns
+             if c.name not in ('summary_id', 'form_date')]
+    names.sort()
+    return names
+
+def describe_changes(table_desc_a, table_desc_b):
+    names_a = col_names(table_desc_a)
+    names_b = col_names(table_desc_b)
+    diffs = DC_Columns(table_desc_a, table_desc_b)
+    s = difflib.SequenceMatcher(None, names_a, names_b)
+    for op, i1, i2, j1, j2 in s.get_opcodes():
+        if op == 'delete':
+            for i in xrange(i1, i2):
+                diffs.col('drop', names_a[i], None)
+        elif op == 'insert':
+            for j in xrange(j1, j2):
+                diffs.col('add', None, names_b[j])
+        elif op == 'equal':
+            for i, j in itertools.izip(xrange(i1, i2), xrange(j1, j2)):
+                diffs.col('', names_a[i], names_b[j])
+        elif op == 'replace':
+            for i in xrange(i1, i2):
+                diffs.col('drop', names_a[i], None)
+            for j in xrange(j1, j2):
+                diffs.col('add', None, names_b[j])
+    return diffs
diff --git a/casemgr/albasetup.py b/casemgr/albasetup.py
new file mode 100644
index 0000000..1d86d2c
--- /dev/null
+++ b/casemgr/albasetup.py
@@ -0,0 +1,295 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+#
+# WARNING - this module relies on deep knowledge of how Albatross works, and
+# the assumptions made by Albatross, and overrides and extends parts of
+# Albatross functionality. In particular, we dynamically create the application
+# and context classes out of the lower level Albatross mixin classes.  This is
+# done to allow the application to be easily deployed in a number of ways, and
+# to record information that would otherwise be discarded or inaccessible.
+#
+
+# Standard Libraries
+import new
+import os
+import sys
+import socket
+import errno
+import time
+
+# 3rd Party
+from albatross import ModularSessionApp, SessionAppContext, \
+                      ModularApp, SimpleAppContext, Redirect
+try:
+    have_branching_session = True
+    from albatross import BranchingSessionContext
+except ImportError:
+    have_branching_session = False
+
+# Application
+from cocklebur import dbobj, datetime, utils
+from casemgr import globals, credentials, messages
+from casemgr import version
+from handle_exception import HandleExceptionMixin
+from wiki.env import Environment
+from wiki.href import Href
+from wiki.formatter import wiki_to_html, wiki_to_oneliner
+
+
+def is_fcgi():
+    # If there's a better way of detecting a FastCGI environment, I'd love to
+    # hear it.
+    try:
+        s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
+                        socket.SOCK_STREAM)
+    except socket.error:
+        return False
+    try:
+        try:
+            s.getpeername()
+        except socket.error, (eno, errmsg):
+            return eno == errno.ENOTCONN
+    finally:
+        s.close()
+
+
+class RequestMixin:
+
+    def get_remote_addr(self):
+        # Remote host - may be intermediate firewall, etc
+        return self.get_param('REMOTE_ADDR', 'unknown')
+
+    def get_forwarded_addr(self):
+        forwarded_addr = (self.get_header('X-Forwarded-For') or 
+                          self.get_header('Forwarded'))
+        if forwarded_addr:
+            return utils.safeprint(forwarded_addr[:80])  # Untrusted data
+
+    def get_remote_host(self):
+        remote_addr = self.get_remote_addr()
+        forwarded_addr = self.get_forwarded_addr()
+        if forwarded_addr:
+            return '%s via %s' % (forwarded_addr, remote_addr)
+        else:
+            return remote_addr
+
+    def get_user_agent(self):
+        return self.get_header('User-Agent')
+
+
+if is_fcgi():
+    try:
+        from albatross import fcgiappnew as fcgiapp
+        print >> sys.stderr, '***** WARNING - using experimental "fcgiappnew"'
+    except ImportError:
+        from albatross import fcgiapp
+
+    class Request(RequestMixin, fcgiapp.Request):
+        pass
+
+    if not hasattr(Request, 'get_param'):
+        # Monkey patch old versions of Albatross < v1.35
+        def get_param(self, key, default=None):
+            return self._Request__fcgi.env.get(key, default)
+        Request.get_param = get_param
+
+    def next_request():
+        while fcgiapp.running():
+            yield Request()
+
+    deploy_mode = 'fcgi'
+else:
+    from albatross import cgiapp
+
+    class Request(RequestMixin, cgiapp.Request):
+        pass
+
+    if not hasattr(Request, 'get_param'):
+        # Monkey patch old versions of Albatross < v1.35
+        def get_param(self, key, default=None):
+            return os.environ.get(key, default)
+        Request.get_param = get_param
+
+    def next_request():
+        yield Request()
+
+    deploy_mode = 'cgi'
+
+
+class ProfilerMixin:
+
+    def run(self, req):
+        '''
+        Process a single browser request
+        Copied from albatross.app - only do this for profiling!
+        '''
+        ctx = None
+        t = time.time()
+        try:
+            ctx = self.create_context()
+            self.profiler.done('create_context')
+            ctx.set_request(req)
+            self.load_session(ctx)
+            self.profiler.done('load_session')
+            self.load_page(ctx)
+            self.profiler.done('load_page')
+            if self.validate_request(ctx):
+                self.merge_request(ctx)
+                self.process_request(ctx)
+            self.profiler.done('validate, merge, process')
+            self.display_response(ctx)
+            self.profiler.done('display_response')
+            self.save_session(ctx)
+            self.profiler.done('save_session')
+            ctx.flush_content()
+            self.profiler.done('flush_content')
+        except Redirect, e:
+            self.save_session(ctx)
+            return ctx.send_redirect(e.loc)
+        except:
+            self.handle_exception(ctx, req)
+        return req.return_code()
+
+
+# A new Context is created for each request
+class CommonAppContext(messages.MessageMixin):
+
+    def __init__(self, app):
+        self.__config_vars = app.config_vars
+        self.__config = app.config
+        self.init_locals()
+        self.set_header('Cache-Control', 'no-cache, no-store')
+        self.set_header('Content-Type', 'text/html; charset=utf-8')
+        self.run_template_once('page_layout.html')
+        self.locals.via_logout = False
+        self.clear_messages()
+
+    def log(self, msg, *args):
+        args = [repr(a) for a in args]
+        print >> sys.stderr, 'LOG:', msg, ' '.join(args)
+
+    def request_elapsed(self):
+        return time.time() - self.locals.request_start
+
+    def appath(self, *args):
+        return '/'.join(('', self.locals.appname) + args)
+
+    def wiki_text(self, text):
+        env = Environment(self.app)
+        env.href = Href(self.__config.appname)
+        text = wiki_to_html(text, env).strip()
+        # FIXME: This will cause badly formed xhtml which we may be concerned
+        # about at some point
+        if text.startswith("<p>"):
+            #text = text[3:]
+            text = '<div class="wikitext">%s</div>' % text
+        return text
+
+    def wiki_oneliner(self, text):
+        env = Environment(self.app)
+        env.href = Href(self.__config.appname)
+        return wiki_to_oneliner(text, env)
+
+    def init_locals(self):
+        self.locals.request_start = time.time()
+        for attr in ('request_elapsed', 'appath', 'wiki_text', 'wiki_oneliner'):
+            setattr(self.locals, attr, getattr(self, attr))
+        for attr in self.__config_vars:
+            setattr(self.locals, attr, getattr(self.__config, attr))
+        self.locals.__version__ = version.__version__
+        self.locals.__svnrev__ = version.__svnrev__
+        self.locals.__pyver__ = sys.version.split(None, 1)[0]
+        self.locals.deploy_mode = deploy_mode
+        self.locals.get_messages = self.get_messages
+        self.locals.have_errors = self.have_errors
+
+    def user_log(self, event_type, **kw):
+        self.locals._credentials.user_log(globals.db, event_type, **kw)
+
+    def admin_log(self, event_type):
+        self.locals._credentials.admin_log(globals.db, event_type)
+
+    def logout(self):
+        self.remove_session()
+        self.init_locals()
+        self.locals.via_logout = True
+        self.set_page('login')
+
+
+def call_all(meth_name):
+    def _call_all(self, *args):
+        for base in self.__class__.__bases__:
+            try:
+                meth = getattr(base, meth_name)
+            except AttributeError:
+                pass
+            else:
+                meth(self, *args)
+    return _call_all
+
+
+def get_app(config, config_vars, profiler=None, ctx_mixins=None, **kwargs):
+    app_bases = [HandleExceptionMixin]
+    if ctx_mixins:
+        ctx_bases = list(ctx_mixins)
+    else:
+        ctx_bases = []
+    if profiler:
+        app_bases.append(ProfilerMixin)
+    if config.session_server:
+        try:
+            sess_serv_host, sess_serv_port = config.session_server.split(':')
+        except ValueError:
+            sess_serv_host, sess_serv_port = config.session_server, 34343
+        else:
+            try:
+                sess_serv_port = int(sess_serv_port)
+            except ValueError:
+                sys.exit('bad session server port specification: %s' % 
+                         sess_serv_port)
+        kwargs['session_appid'] = config.appname
+        kwargs['session_server'] = sess_serv_host
+        kwargs['server_port'] = sess_serv_port
+        if config.session_timeout:
+            kwargs['session_age'] = int(config.session_timeout)
+        else:
+            kwargs['session_age'] = 600
+        app_bases.append(ModularSessionApp)
+        if have_branching_session:
+            ctx_bases.append(BranchingSessionContext)
+        else:
+            ctx_bases.append(SessionAppContext)
+    else:
+        app_bases.append(ModularApp)
+        ctx_bases.append(SimpleAppContext)
+    ctx_bases.append(CommonAppContext)
+    kwargs['secret'] = config.session_secret
+    # This is a *little* gross... create a class on the fly
+    ctx_cls = new.classobj('AlbaCtx', tuple(ctx_bases), 
+                           dict(__init__=call_all('__init__')))
+    def create_context(self):
+        return ctx_cls(self)
+    app_cls = new.classobj('AlbaApp', tuple(app_bases), 
+                           dict(create_context=create_context))
+    app = app_cls(**kwargs)
+    if profiler:
+        app.profiler = profiler
+    app.config = config
+    app.config_vars = config_vars
+    return app
diff --git a/casemgr/bulletins.py b/casemgr/bulletins.py
new file mode 100644
index 0000000..18deb59
--- /dev/null
+++ b/casemgr/bulletins.py
@@ -0,0 +1,76 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+
+from mx import DateTime
+
+double_nl_re = re.compile('\n\n', re.MULTILINE)
+nl_re = re.compile('\n', re.MULTILINE)
+
+
+class Bulletin:
+    def __init__(self, bulletin_row):
+        self.bulletin_id = bulletin_row.bulletin_id
+        self.post_date = bulletin_row.post_date
+        self.expiry_date = bulletin_row.expiry_date
+        self.title = bulletin_row.title
+        self.synopsis = bulletin_row.synopsis
+        self.detail = bulletin_row.detail
+
+class Bulletins:
+    def __init__(self, db, credentials):
+        self.db = db
+        self.credentials = credentials
+        self.bulletins = None
+
+    def __getstate__(self):
+        return self.db, self.credentials
+
+    def __setstate__(self, state):
+        self.db, self.credentials = state
+        self.bulletins = None
+
+    def load(self, hide_time=None):
+        query = self.db.query('bulletins', 
+                            distinct = True, order_by = 'post_date DESC')
+        if 'ACCESSALL' not in self.credentials.rights:
+            query.join('JOIN group_bulletins USING (bulletin_id)')
+            query.join('JOIN unit_groups USING (group_id)')
+            query.where('unit_groups.unit_id = %s', 
+                        self.credentials.unit.unit_id)
+        if hide_time:
+            query.where('bulletins.post_date > %s', 
+                        DateTime.DateTimeFromTicks(hide_time))
+        sub = query.sub_expr('OR')
+        sub.where('bulletins.post_date is null')
+        sub.where('bulletins.post_date <= CURRENT_TIMESTAMP')
+        sub = query.sub_expr('OR')
+        sub.where('bulletins.expiry_date is null')
+        sub.where('bulletins.expiry_date > CURRENT_TIMESTAMP')
+        self.bulletins = query.fetchall()
+
+    def get_bulletins(self, hide_time):
+        if self.bulletins is None:
+            self.load(hide_time)
+        return self.bulletins
+
+    def get_bulletin(self, bulletin_id):
+        query = self.db.query('bulletins')
+        query.where('bulletin_id = %s', bulletin_id)
+        return Bulletin(query.fetchone())
+
diff --git a/casemgr/cached.py b/casemgr/cached.py
new file mode 100644
index 0000000..3460f2f
--- /dev/null
+++ b/casemgr/cached.py
@@ -0,0 +1,62 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from time import time
+from casemgr import globals
+
+class Cached(object):
+    """
+    Cache a set of objects, periodically reloading.
+
+    Subclasses call the .refresh() method on each access, and implement
+    a .load() method, which will be called by .refresh() when the cache
+    has expired.
+    """
+    load_time = 0
+    time_to_live = 120
+
+    def refresh(self):
+        now = time()
+        if self.load_time + self.time_to_live < now:
+            self.load()
+            self.load_time = now
+
+    def cache_invalidate(self, *a):
+        # Force a reload on next refresh
+        self.load_time = 0
+
+
+class NotifyCache(Cached):
+    """
+    Cache of objects with optional refresh on change notification.
+
+    This elaborates on the Cached class - if a notification event arrives, the
+    object is scheduled for refresh on next access (by zeroing the load time).
+
+    If change notifications are available, the cache time-to-live is
+    increased ten-fold.
+
+    Note that this class is only suitable for objects cached in the
+    application, not objects in the client context.
+    """
+    notification_target = None
+
+    def __init__(self):
+        if (self.notification_target and
+            globals.notify.subscribe(self.notification_target, 
+                                     self.cache_invalidate)):
+            self.time_to_live *= 10
diff --git a/casemgr/caseaccess.py b/casemgr/caseaccess.py
new file mode 100644
index 0000000..d83a9ff
--- /dev/null
+++ b/casemgr/caseaccess.py
@@ -0,0 +1,52 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+def acl_query(query, credentials, deleted=False):
+    """
+    This adds a "where" clause to the given /query/ to limit the query to
+    the subset of cases and/or contacts that this unit has access to (as
+    given by /credentials/). 
+
+    The where clause is implemented as an "IN" subselect returning the
+    visible case_id's.
+    """
+    if 'ACCESSALL' not in credentials.rights:
+        or_query = query.sub_expr('OR')
+        if 'ACCESSSYND' in credentials.rights:
+            accessquery = or_query.in_select('syndrome_id', 'group_syndromes')
+            accessquery.join('JOIN unit_groups USING (group_id)')
+            accessquery.where('unit_id = %s', credentials.unit.unit_id)
+        else:
+            accessquery = or_query.in_select('case_id', 'case_acl')
+            accessquery.where('unit_id = %s', credentials.unit.unit_id)
+        taskquery = or_query.in_select('case_id', 'tasks', 
+                                        columns=['case_id'])
+        queuequery = taskquery.in_select('queue_id', 'workqueues')
+        queuequery.where('user_id = %s OR unit_id = %s',
+                         credentials.user.user_id, credentials.unit.unit_id)
+        memberquery = queuequery.union_query('workqueue_members')
+        memberquery.where('user_id = %s OR unit_id = %s',
+                          credentials.user.user_id, credentials.unit.unit_id)
+    if deleted in ('y', True, 'True'):
+        query.where('cases.deleted')
+    elif deleted in ('n', False, 'False'):
+        query.where('not cases.deleted')
+
+def contact_query(query, case_id):
+    inq = query.in_select('case_id', 'case_contacts')
+    inq.where('contact_id = %s', case_id)
diff --git a/casemgr/caseassignment.py b/casemgr/caseassignment.py
new file mode 100644
index 0000000..ed7a03c
--- /dev/null
+++ b/casemgr/caseassignment.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+
+from casemgr import syndcategorical
+
+class SyndromeCaseAssignmentInfo(syndcategorical.SyndromeCategoricalInfo):
+
+    title = 'Case Assignment Values'
+    table = 'syndrome_case_assignments'
+    order_by = 'syndca_id'
+    explicit_order = True
+    defaults = [
+        ('A', 'Assignment A'),
+        ('B', 'Assignment B'),
+        ('C', 'Assignment C'),
+    ]
+
+
+class EditSyndromeCaseAssignment(SyndromeCaseAssignmentInfo, 
+                                 syndcategorical.EditSyndromeCategorical):
+
+    pass
+
+
+class SyndromesCaseAssignment(SyndromeCaseAssignmentInfo,
+                              syndcategorical.SyndromeCategorical):
+
+    pass
+
+
+_syndromes_case_assignments = SyndromesCaseAssignment()
+get_syndrome = _syndromes_case_assignments.get_syndrome
+optionexpr = _syndromes_case_assignments.optionexpr
+get_label = _syndromes_case_assignments.get_label
+normalise_status = _syndromes_case_assignments.normalise
diff --git a/casemgr/casedupe.py b/casemgr/casedupe.py
new file mode 100644
index 0000000..71df266
--- /dev/null
+++ b/casemgr/casedupe.py
@@ -0,0 +1,118 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from casemgr import globals, paged_search, person, demogfields, \
+                    casetags, syndrome
+
+class CaseSummary:
+    def __init__(self, row):
+        self.row = row
+
+    def summary(self):
+        return fields.summary(self.row)
+
+
+class CaseDupeScan(paged_search.PagedSearch):
+    order_by = 'surname', 'given_names', 'person_id', 'notification_datetime'
+    case_fields = (
+        'case_id', 'local_case_id', 'case_status', 
+        'onset_datetime', 'notification_datetime',
+    )
+
+    def __init__(self, prefs, syndrome_id, notification_window):
+        paged_search.PagedSearch.__init__(self, globals.db, prefs, None)
+        self.syndrome_id = syndrome_id
+        self.syndrome_name = syndrome.syndromes[syndrome_id].name
+        self.notification_window = notification_window
+        cases_by_person = {}
+        cols = 'person_id', 'case_id'
+        query = globals.db.query('cases', order_by=self.order_by)
+        query.where('NOT deleted')
+        query.join('JOIN persons USING (person_id)')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        for person_id, case_id in query.fetchcols(cols):
+            person_case_ids = cases_by_person.setdefault(person_id, [])
+            person_case_ids.append(case_id)
+        cases_by_person = [(person_id, cases)
+                           for person_id, cases in cases_by_person.iteritems()
+                           if len(cases) >= 2]
+        if not cases_by_person:
+            raise globals.Error('No duplicates found')
+        self.cases_by_person = dict(cases_by_person)
+        # Put person ids in order
+        person_ids = self.cases_by_person.keys()
+        query = globals.db.query('persons', order_by='surname, given_names')
+        query.where_in('person_id', person_ids)
+        self.pkeys = query.fetchcols('person_id')
+        # fields
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        fields = fields.context_fields('result')
+        self.fields = []
+        for name in self.case_fields:
+            try:
+                self.fields.append(fields.field_by_name(name))
+            except KeyError:
+                pass
+
+    def page_rows(self):
+        person_ids = self.page_pkeys()
+        page_case_ids = set()
+        for person_id in person_ids:
+            page_case_ids.update(self.cases_by_person[person_id])
+        # Fetch persons
+        query = globals.db.query('persons')
+        query.where_in('person_id', person_ids)
+        person_by_id = {}
+        for row in query.fetchall():
+            person_by_id[row.person_id] = person.person(row)
+        # Fetch cases
+        query = globals.db.query('cases')
+        query.where_in('case_id', page_case_ids)
+        query.where('NOT deleted')
+        case_by_id = {}
+        for row in query.fetchall():
+            case_by_id[row.case_id] = CaseSummary(row)
+        # Fetch case tags 
+        cases_tags = casetags.CasesTags(page_case_ids)
+        # Now join them up
+        persons = []
+        for person_id in person_ids:
+            case_ids = self.cases_by_person[person_id]
+            try:
+                pers = person_by_id[person_id]
+            except KeyError:
+                continue
+            pers.cases = []
+            for case_id in case_ids:
+                try:
+                    pers.cases.append(case_by_id[case_id])
+                except KeyError:
+                    continue
+            if len(pers.cases) < 2:
+                continue
+            for cs in pers.cases:
+                cs.row.tags = cases_tags.get(cs.row.case_id)
+            persons.append(pers)
+        return persons
+
+
diff --git a/casemgr/casemerge.py b/casemgr/casemerge.py
new file mode 100644
index 0000000..792d735
--- /dev/null
+++ b/casemgr/casemerge.py
@@ -0,0 +1,414 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import copy
+from cocklebur import dbobj, datetime
+from casemgr import globals, caseaccess, demogfields, syndrome, person, casetags
+import config
+
+class MergeError(globals.Error): pass
+class CaseHasChanged(MergeError): pass
+
+class NS: pass
+
+
+class CaseField:
+
+    def __init__(self, merge, field):
+        self.merge = merge
+        self.name = field.name
+        self.label = field.label
+        self.field = field.name
+
+    def outtrans(self, ns):
+        return self.merge.demogfield(self.name).outtrans(ns) or ''
+
+    def format(self):
+        return self.merge.demogfield(self.name).format()
+
+
+class MergeField:
+
+    def __init__(self, merge, field):
+        self.merge = merge
+        self.name = field.name
+        self.field = 'casemerge.case.%s' % field.name
+        self.render = getattr(field, 'render_case', None) or field.render
+        self.disabled = False
+
+    def optionexpr(self):
+        return self.merge.demogfield(self.name).optionexpr()
+
+    def format(self):
+        return self.merge.demogfield(self.name).format()
+
+
+class MergeCol:
+
+    field_cls = MergeField
+
+    def __init__(self, merge, field, index):
+        self.merge = merge
+        self.name = field.name
+        self.label = field.label
+        value_a = getattr(merge.case_a, self.name)
+        value_b = getattr(merge.case_b, self.name)
+        self.conflict = (value_a and value_b and value_a != value_b)
+        self.field = None
+        if self.field_cls is not None:
+            self.field = self.field_cls(merge, field)
+        if not value_a and not value_b:
+            self.source = 'd'
+        elif value_b and not value_a:
+            self.source = 'b'
+        else:
+            self.source = 'a'
+        self.source_field = 'casemerge.fields[%d].source' % index
+        self.show_radio = (self.name != 'tags')
+
+    def outtrans(self, ns):
+        return self.merge.demogfield(self.name).outtrans(ns) or ''
+
+    def desc_edit(self):
+        value_a = getattr(self.merge.case_a, self.name)
+        value_b = getattr(self.merge.case_b, self.name)
+        value_e = getattr(self.merge.case, self.name, None)
+        if value_e:
+            op = 'Edit'
+            hilite = value_e != value_a or value_e != value_b
+            ns = self.merge.case
+        elif self.source == 'a' and value_a != value_b:
+            op = 'A'
+            hilite = True
+            ns = self.merge.case_a
+        elif self.source == 'b' and value_a != value_b:
+            op = 'B'
+            hilite = True
+            ns = self.merge.case_b
+        elif self.source == 'd' and (value_a or value_b):
+            op = 'DELETE'
+            hilite = True
+            if value_a:
+                ns = self.merge.case_a
+            else:
+                ns = self.merge.case_b
+        else:
+            return None
+        return self.label, op, self.outtrans(ns), hilite
+
+    def apply(self, case_a, case_b):
+        value_a = getattr(case_a, self.name)
+        value_b = getattr(case_b, self.name)
+        initial_a = getattr(self.merge.case_a, self.name)
+        initial_b = getattr(self.merge.case_b, self.name)
+        if initial_a != value_a or initial_b != value_b:
+            raise CaseHasChanged
+        value_e = getattr(self.merge.case, self.name)
+        if value_e:
+            assert self.field is not None
+            value = value_e
+        elif self.source == 'a':
+            value = value_a
+        elif self.source == 'b':
+            value = value_b
+        elif self.source == 'd':
+            assert self.field is not None
+            value = None
+        setattr(case_a, self.name, value)
+        setattr(case_b, self.name, value)
+        return value_a != value, value_b != value
+
+
+class CaseMerge:
+
+    def __init__(self, id_a, id_b):
+        self.id_a = id_a
+        self.id_b = id_b
+        self.fields = None
+        self.case = NS()
+        self.init_fields(*self._fetch_cases())
+        self.keep = 'a'
+
+    def _fetch_cases(self, for_update=False):
+        query = globals.db.query('cases', for_update=for_update)
+        query.where_in('case_id', (self.id_a, self.id_b))
+        try:
+            a, b = query.fetchall()
+        except ValueError:
+            raise MergeError('Error fetching records (incorrect count)')
+        cases_tags = casetags.CasesTags((a.case_id, b.case_id))
+        a.tags = cases_tags.get(a.case_id)
+        b.tags = cases_tags.get(b.case_id)
+        if a.case_id == self.id_a and b.case_id == self.id_b:
+            return a, b
+        elif a.case_id == self.id_b and b.case_id == self.id_a:
+            return b, a
+        else:
+            raise MergeError('Error fetching records (incorrect records)')
+
+    def syndrome(self):
+        return syndrome.syndromes[self.syndrome_id]
+
+    def demogfields(self):
+        return demogfields.get_demog_fields(globals.db, self.syndrome_id)
+
+    # AM 20091007 - not used? 
+    #def casefields(self):
+    #    return [CaseField(self, field) 
+    #            for field in self.demogfields().context_fields('case')
+    #            if field.field.startswith('case.case_row')]
+
+    def demogfield(self, name):
+        return self.demogfields().field_by_name(name)
+
+    def add_field(self, merge_cls, field):
+        self.fields.append(merge_cls(self, field, len(self.fields)))
+
+    def init_fields(self, case_a, case_b):
+        if case_a.syndrome_id != case_b.syndrome_id:
+            raise MergeError('Error fetching records (syndrome mismatch)')
+        self.syndrome_id = case_a.syndrome_id
+        if case_a.deleted and not case_b.deleted:
+            self.keep = 'b'
+        self.css_a = self.css_b = ''
+        if case_a.deleted:
+            self.css_a = 'gray'
+        if case_b.deleted:
+            self.css_b = 'gray'
+        self.case_a = NS()
+        self.case_b = NS()
+        self.fields = []
+        ignore_fields = ('case_id', 'deleted',
+                         'delete_reason', 'delete_timestamp')
+        for field in self.demogfields():
+            try:
+                value_a = getattr(case_a, field.name)
+                value_b = getattr(case_b, field.name)
+            except AttributeError:
+                continue
+            setattr(self.case_a, field.name, value_a)
+            setattr(self.case_b, field.name, value_b)
+            if field.name not in ignore_fields:
+                self.add_field(MergeCol, field)
+        if not getattr(self.case, 'tags', None):
+            self.case.tags = casetags.Tags()
+            if self.case_a.tags:
+                self.case.tags.update(self.case_a.tags)
+            if self.case_b.tags:
+                self.case.tags.update(self.case_b.tags)
+
+    def desc_edit(self):
+        edits = []
+        for mc in self.fields:
+            desc = mc.desc_edit()
+            if desc:
+                edits.append(desc)
+        return edits
+
+    def merge(self, credentials):
+        # lock the relevent cases
+        case_a, case_b = self._fetch_cases(for_update=True)
+        # Safety checks:
+        not_same_person = (case_a.person_id != case_b.person_id)
+        syndrome_mismatch = (case_a.syndrome_id != case_b.syndrome_id)
+        if not_same_person or syndrome_mismatch:
+            raise MergeError('Unable to merge - consistency check failed')
+        for mc in self.fields:
+            try:
+                mc.apply(case_a, case_b)
+            except CaseHasChanged:
+                case_a.db_revert()
+                case_b.db_revert()
+                self.init_fields(case_a, case_b)
+                raise
+        # Which direction to merge?
+        if self.keep == 'a':
+            update_case, delete_case = case_a, case_b
+        else:
+            update_case, delete_case = case_b, case_a
+        update_desc = update_case.db_desc()
+        delete_desc = delete_case.db_desc()
+        # XXX Describe tag changes - how?
+        if not update_desc:
+            update_desc = 'no edits required'
+        if not delete_desc:
+            delete_desc = 'no edits required'
+        if update_case.deleted and not delete_case.deleted:
+            update_case.deleted = False
+            update_case.delete_reason = None
+            update_case.delete_timestamp = None
+        delete_case.deleted = True
+        delete_case.delete_reason = 'Merged to %s' % update_case.case_id
+        delete_case.delete_timestamp = datetime.now()
+        curs = globals.db.cursor()
+        try:
+            # merge contacts
+            dbobj.execute(curs, 'UPDATE case_contacts SET contact_id=%s'
+                                ' WHERE contact_id=%s'
+                                '  AND case_id != %s'
+                                '  AND case_id NOT IN'
+                                '   (SELECT case_id FROM case_contacts'
+                                '     WHERE contact_id=%s)',
+                            (update_case.case_id, delete_case.case_id,
+                             update_case.case_id, update_case.case_id))
+            dbobj.execute(curs, 'UPDATE case_contacts SET case_id=%s'
+                                ' WHERE case_id=%s'
+                                '  AND contact_id != %s'
+                                '  AND contact_id NOT IN'
+                                '   (SELECT contact_id FROM case_contacts'
+                                '     WHERE case_id=%s)',
+                            (update_case.case_id, delete_case.case_id,
+                             update_case.case_id, update_case.case_id))
+            dbobj.execute(curs, 'DELETE FROM case_contacts'
+                                ' WHERE case_id=%s OR contact_id=%s',
+                            (delete_case.case_id, delete_case.case_id))
+
+            # case_form_summary
+            dbobj.execute(curs, 'UPDATE case_form_summary SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+            # case_acl
+            dbobj.execute(curs, 'UPDATE case_acl SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+            # tasks
+            dbobj.execute(curs, 'UPDATE tasks SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+            dbobj.execute(curs, 'UPDATE tasks SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+            dbobj.execute(curs, 'UPDATE user_log SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+            dbobj.execute(curs, 'UPDATE user_log SET case_id=%s'
+                                ' WHERE case_id=%s',
+                          (update_case.case_id, delete_case.case_id))
+        finally:
+            curs.close()
+        update_case.db_update()
+        tag_desc = casetags.set_case_tags(update_case.case_id, update_case.tags)
+        delete_case.db_update()
+        desc = 'Merge System ID %s into %s, UPDATED %s %s, DELETED %s' %\
+                    (delete_case.case_id, update_case.case_id, 
+                     update_desc, tag_desc, delete_desc)
+        credentials.user_log(globals.db, desc, case_id=update_case.case_id)
+        return update_case, delete_case
+
+
+class SyndromeCaseMergeSet(list):
+
+    def __init__(self, syndrome_id):
+        self.syndrome_id = syndrome_id
+        self.name = syndrome.syndromes[syndrome_id].name
+
+    def __cmp__(self, other):
+        return cmp(self.name, other.name)
+
+
+class SelCaseMerge:
+
+    def __init__(self, credentials, syndrome_id, query, person):
+        self.syndrome_id = syndrome_id
+        self.query = query
+        self.person = person
+#        self.update()
+
+    def update(self):
+        cases = self.query.fetchall()
+        case_ids = [case.case_id for case in cases]
+        cases_tags = casetags.CasesTags(case_ids)
+        syndsets = {}
+        for case in cases:
+            case.tags = cases_tags.get(case.case_id)
+            syndset = syndsets.get(case.syndrome_id)
+            if syndset is None:
+                syndset = SyndromeCaseMergeSet(case.syndrome_id)
+                syndsets[case.syndrome_id] = syndset
+            syndset.append(case)
+        self.syndsets = [syndset for syndset in syndsets.values()
+                         if len(syndset) > 1]
+        self.syndsets.sort()
+        self.index_a = '0,0'
+        self.index_b = '0,1'
+
+    def __nonzero__(self):
+        return bool(self.syndsets)
+
+    def personfields_rows_and_cols(self):
+        personfields = []
+        for field in self.demogfields().context_fields('form'):
+            if field.field.startswith('case.person'):
+                field = copy.copy(field)
+                field.field = field.field.replace('case.person', 
+                                                  'selcasemerge.person')
+                personfields.append(field)
+        return demogfields.rows_and_cols(personfields)
+
+    def demogfields(self):
+        return demogfields.get_demog_fields(globals.db, self.syndrome_id)
+
+    def casefields(self):
+        return [CaseField(self, field) 
+                for field in self.demogfields().context_fields('case')
+                if (field.field.startswith('case.case_row') 
+                    or field.name == 'tags')]
+
+    def demogfield(self, name):
+        return self.demogfields().field_by_name(name)
+
+    def get_casemerge(self):
+        try:
+            if not self.index_a or not self.index_b:
+                raise ValueError
+            set_a_idx, case_a_idx = self.index_a.split(',')
+            set_b_idx, case_b_idx = self.index_b.split(',')
+            if set_a_idx != set_b_idx:
+                raise MergeError('Select A and B records from the same %s' %
+                                 config.syndrome_label)
+            syndset = self.syndsets[int(set_a_idx)]
+            case_a = syndset[int(case_a_idx)]
+            case_b = syndset[int(case_b_idx)]
+            if case_a is case_b: raise ValueError
+        except (IndexError, ValueError, TypeError):
+            raise MergeError('Select the two records you wish to merge')
+        return CaseMerge(case_a.case_id, case_b.case_id)
+
+
+def by_person(credentials, person_row):
+    query = globals.db.query('cases', order_by='case_id')
+    caseaccess.acl_query(query, credentials, deleted=None)
+    query.where('person_id = %s', person_row.person_id)
+    return SelCaseMerge(credentials, None, query, person.edit_row(person_row))
+
+
+def by_person_id(credentials, syndrome_id, person_id):
+    query = globals.db.query('cases', order_by='case_id')
+    caseaccess.acl_query(query, credentials, deleted=None)
+    query.where('syndrome_id = %s', syndrome_id)
+    query.where('person_id = %s', person_id)
+    return SelCaseMerge(credentials, None, query, person.edit_id(person_id))
+
+
+def by_case(credentials, case, person):
+    query = globals.db.query('cases', order_by='case_id')
+    caseaccess.acl_query(query, credentials, deleted=None)
+    query.where('person_id = %s', case.person_id)
+    query.where('syndrome_id = %s', case.syndrome_id)
+    return SelCaseMerge(credentials, case.syndrome_id, query, person)
+
diff --git a/casemgr/cases.py b/casemgr/cases.py
new file mode 100644
index 0000000..2e86e8d
--- /dev/null
+++ b/casemgr/cases.py
@@ -0,0 +1,348 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import time
+import re
+from cocklebur import dbobj, datetime, pt
+from casemgr import globals, person, form_summary, demogfields, \
+                    syndrome, casestatus, caseaccess, casetags
+
+import config
+
+ValidationError = dbobj.ValidationError
+
+def local_case_id_validate(local_case_id):
+    if not local_case_id:
+        return
+#    if len(local_case_id) > 10:
+#        raise dbobj.ValidationError('Local ID must be 10 characters or less')
+    if not re.match('^[a-zA-Z0-9]+$', local_case_id):
+        raise dbobj.ValidationError('Local ID must be alphanumeric')
+
+class ACL(pt.SearchPT):
+    def __init__(self, credentials, case_row):
+        self.credentials = credentials
+        ptable = globals.db.participation_table('case_acl','case_id','unit_id')
+        ptable.preload_from_result([case_row])
+        pt.SearchPT.__init__(self, ptable[case_row.case_id], 'name',
+                             filter='enabled', info_page=True)
+
+    def remove(self, index):
+        if self.pt_set[int(index)].unit_id == self.credentials.unit.unit_id:
+            self.search_error = 'Can\'t delete your unit\'s access'
+            return
+        pt.SearchPT.remove(self, index)
+
+
+class Case(form_summary.FormsListMixin):
+
+    def __init__(self, credentials, case_row, seed_person=None):
+        if case_row is None:
+            raise dbobj.DatabaseError('Access denied')
+        self.acl = None
+        self.case_row = case_row
+        self.deleted = bool(self.case_row.deleted)
+        self.delete_reason = self.case_row.delete_reason
+        self.delete_timestamp = self.case_row.delete_timestamp
+        self.syndrome = syndrome.syndromes[case_row.syndrome_id]
+        self.credentials = credentials
+        self.tags = casetags.CaseTags(self.case_row.case_id)
+        self._contact_count = None
+        form_summary.FormsListMixin.__init__(self, case_row.syndrome_id)
+        if self.case_row.is_new():
+            self.person = person.edit_new(seed_person)
+        else:
+            self.person = person.edit_id(self.case_row.person_id)
+        self.forms.set_case(self.case_row.case_id)
+
+    def load_acl(self):
+        if self.case_row.case_id:
+            self.acl = ACL(self.credentials, self.case_row)
+    
+    def unload_acl(self):
+        self.acl = None
+
+    def viewonly(self):
+        return self.deleted or 'VIEWONLY' in self.credentials.rights
+
+    def assert_not_viewonly(self):
+        if self.viewonly():
+            raise ValidationError('Case is view-only')
+
+    def __str__(self):
+        parts = []
+        if self.person.surname:
+            parts.append(self.person.surname)
+        if self.person.given_names:
+            if parts:
+                parts.append(', ')
+            parts.append(self.person.given_names)
+        if self.case_row.local_case_id:
+            if parts:
+                parts.append(' ')
+            parts.append('[%s]' % self.case_row.local_case_id)
+        if self.case_row.case_id:
+            if parts:
+                parts.append(' ')
+            parts.append('(ID %s)' % self.case_row.case_id)
+        return ''.join(parts)
+
+    def title(self):
+        if self.is_new():
+            title = 'Add'
+        elif self.viewonly():
+            title = 'View'
+        else:
+            title = 'Edit'
+        title += ' Case'
+        count = self.contact_count()
+        if count == 1:
+            title = '%s - 1 %s' % (title, config.contact_label.lower())
+        else:
+            title = '%s - %d %ss' % (title, count, config.contact_label.lower())
+        return title
+
+    def new(cls, credentials, syndrome_id, 
+            from_search=None, use_person_id=None, 
+            defer_case_id=False, **kwargs):
+        """
+        Alternate constructor, used when case is new
+        """
+        if defer_case_id:
+            case_id = None
+        else:
+            case_id = globals.db.nextval('cases', 'case_id')
+        case_seed = dict(case_id=case_id,
+                         syndrome_id=syndrome_id,
+                         notification_datetime=datetime.now())
+        if from_search is not None:
+            if use_person_id is None:
+                kwargs['seed_person'] = from_search.person
+            if from_search.case_status != '!':
+                case_seed['case_status'] = from_search.case_status
+            if from_search.case_assignment != '!':
+                case_seed['case_assignment'] = from_search.case_assignment
+            case_seed['local_case_id'] = from_search.local_case_id
+            # case_seed['notes'] = from_search.notes
+            # case_seed['notification_datetime'] = from_search.notification_datetime
+            # case_seed['onset_datetime'] = from_search.onset_datetime
+            case_seed['notifier_name'] = from_search.notifier_name
+            # case_seed['notifier_contact'] = from_search.notifier_contact
+        case_row = globals.db.new_row('cases', **case_seed)
+        case = cls(credentials, case_row, **kwargs)
+        if use_person_id is not None:
+            case.use_person_id(use_person_id)
+        if from_search and from_search.tags:
+            # This doesn't give the normal "seed" semantics.
+            case.tags.cur = from_search.tags
+        return case
+    new = classmethod(new)
+
+    def get_demog_fields(self, context):
+        disabled = self.viewonly() or bool(self.person.data_src)
+        syndrome_id = self.case_row.syndrome_id
+        fields = demogfields.get_demog_fields(globals.db, syndrome_id)
+        return fields.context_fields(context, disabled=disabled)
+
+    def use_person_id(self, person_id):
+        self.person = person.edit_id(person_id)
+
+    def is_new(self):
+        return self.case_row.is_new()
+
+    def field_label(self, field):
+        # get_demog_fields() and field_by_name() internally cache lookups
+        demog_fields = demogfields.get_demog_fields(globals.db, 
+                                                    self.case_row.syndrome_id)
+        return demog_fields.field_by_name(field).label
+
+    def validate(self):
+        _label = self.field_label
+        try:
+            self.person.normalise()
+            self.person.validate()
+        except person.Error, e:
+            raise ValidationError('%s' % e)
+        case_row = self.case_row
+        if not case_row.local_case_id and not self.person.surname:
+            raise ValidationError('Either %s or %s must be specified' %
+                                  (_label('surname'), _label('local_case_id')))
+        local_case_id_validate(case_row.local_case_id)
+        try:
+            case_row.onset_datetime = \
+                datetime.mx_parse_datetime(case_row.onset_datetime)
+        except datetime.Error, e:
+            raise ValidationError('%s: %s' % (_label('onset_datetime'), e))
+        try:
+            case_row.notification_datetime = \
+                datetime.mx_parse_datetime(case_row.notification_datetime)
+        except datetime.Error, e:
+            raise ValidationError('%s: %s' % 
+                                  (_label('notification_datetime'), e))
+        if datetime.is_later_than(self.case_row.onset_datetime,
+                                  self.case_row.notification_datetime):
+            raise ValidationError('%s must be after %s' %
+                                  (_label('notification_datetime'), 
+                                   _label('onset_datetime')))
+        if datetime.is_later_than(self.person.DOB,
+                                  self.case_row.onset_datetime):
+            raise ValidationError('%s must be after %s' %
+                                  (_label('onset_datetime'), _label('DOB')))
+        if datetime.is_later_than(self.person.DOB, datetime.now()):
+            raise ValidationError('\'%s\': %s must not be in the future' %
+                    (self.person.DOB, _label('DOB')))
+        if datetime.is_later_than(self.case_row.onset_datetime, datetime.now()):
+            raise ValidationError('\'%s\': %s must not be in the future' %
+                    (self.case_row.onset_datetime, _label('onset_datetime')))
+        if datetime.is_later_than(self.case_row.notification_datetime,
+                                  datetime.now()):
+            raise ValidationError('\'%s\': %s must not be in the future' %
+                    (self.case_row.notification_datetime, 
+                     _label('notification_datetime')))
+
+    def has_changed(self):
+        return (
+            self.tags.has_changed() or
+            self.case_row.db_has_changed() or 
+            self.person.has_changed()
+        )
+
+    def db_desc(self):
+        desc = [
+            self.person.db_desc(), 
+            self.case_row.db_desc(), 
+            self.tags.desc(),
+        ]
+        return ', '.join([d for d in desc if d])
+
+    def cc_notify(self):
+        """
+        Syndrome case count has changed, send notification
+        """
+        globals.notify.notify('syndromecasecount', self.case_row.syndrome_id)
+
+    def update(self):
+        self.assert_not_viewonly()
+        self.validate()
+        is_new = self.is_new()
+        self.person.db_update()
+        self.case_row.person_id = self.person.person_id
+        try:
+            self.case_row.db_update()
+        except dbobj.RecordDeleted:
+            raise dbobj.RecordDeleted('Record has been deleted (or merged) by another user')
+        self.tags.update(self.case_row.case_id)
+        if is_new:
+            case_acl = globals.db.new_row('case_acl')
+            case_acl.case_id = self.case_row.case_id
+            case_acl.unit_id = self.credentials.unit.unit_id
+            case_acl.db_update()
+            self.cc_notify()
+        self.forms.set_case(self.case_row.case_id)
+
+    def revert(self):
+        self.person.db_revert()
+        self.case_row.db_revert()
+
+    def delete(self):
+        # NOTE - not longer used - cases are logically deleted.
+        #
+        # This method is unusual in that it performs a db.commit(). This is so
+        # the person delete can be rolled back without rolling back the case
+        # delete. 
+        query = globals.db.query('case_form_summary')
+        query.where('case_id = %s', self.case_row.case_id)
+        query.delete()
+        self.case_row.db_delete()
+        globals.db.commit()
+        try:
+            self.person.db_delete()
+        except (dbobj.IntegrityError, dbobj.OperationalError):
+            # Probably linked to other cases.
+            globals.db.rollback()
+        else:
+            globals.db.commit()
+        self.cc_notify()
+
+    def user_log(self, event_type):
+        try:
+            self.credentials.user_log(globals.db, event_type,
+                                      case_id=self.case_row.case_id)
+        except dbobj.ConstraintError:
+            raise dbobj.RecordDeleted('Record has been deleted (or merged) by another user')
+
+    def contact_count(self):
+        if self._contact_count is None:
+            query = globals.db.query('cases')
+            caseaccess.contact_query(query, self.case_row.case_id)
+            query.where('not deleted')
+            self._contact_count = query.aggregate('count(*)')
+        return self._contact_count
+
+    def invalidate_contact_count(self):
+        self._contact_count = None
+
+    def rows_and_cols(self, context):
+        return self.get_demog_fields(context).rows_and_cols()
+
+    def init_tabs(self, select=None):
+        self.tabs = self.get_demog_fields('case').tabs(select)
+
+    def set_deleted(self, delete, reason=None):
+        if delete:
+            timestamp = datetime.now().mx()
+        else:
+            timestamp = None
+            reason = None
+        query = globals.db.query('cases')
+        query.where('case_id = %s', self.case_row.case_id)
+        query.update('deleted=%s, delete_reason=%s, delete_timestamp=%s',
+                     delete, reason, timestamp)
+        globals.db.commit()
+        self.deleted = delete
+        self.delete_reason = reason
+        self.delete_timestamp = timestamp
+
+
+def edit_case(credentials, case_id):
+    query = globals.db.query('cases')
+    query.where('case_id = %s', case_id)
+    caseaccess.acl_query(query, credentials, deleted=None)
+    return Case(credentials, query.fetchone())
+
+new_case = Case.new
+
+
+def case_query(credentials, **kwargs):
+    """
+    Look up a (single) case via kwargs (local_case_id, etc)
+    """
+    query = globals.db.query('cases')
+    for col, val in kwargs.iteritems():
+        query.where('%s = %%s' % col, val)
+    caseaccess.acl_query(query, credentials, deleted=None)
+    row = query.fetchone()
+    if row is None:
+        return None
+    return Case(credentials, query.fetchone())
+
+
+def edit_form(credentials, summary_id):
+    ef = form_summary.edit_form(summary_id)
+    case = edit_case(credentials, ef.case_id)
+    return case, ef
+
diff --git a/casemgr/caseset.py b/casemgr/caseset.py
new file mode 100644
index 0000000..394791f
--- /dev/null
+++ b/casemgr/caseset.py
@@ -0,0 +1,303 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import cPickle as pickle
+
+from cocklebur import dbobj
+from casemgr import globals, cases, caseaccess, cached
+
+import config
+
+class CaseSetBase(object):
+
+    dynamic = False
+    caseset_id = None                   # Not saved
+    case_ids = ()
+    name = ''
+    index = 0
+
+    def load(self, credentials):
+        pass
+
+    def __len__(self):
+        return len(self.case_ids)
+
+    def edit_cur(self, credentials):
+        if self.index >= len(self):
+            self.index = len(self) - 1
+        return cases.edit_case(credentials, self.case_ids[self.index])
+
+    def inrange(self, offset):
+        return 0 <= (self.index + offset) < len(self.case_ids)
+
+    def remove(self, case_id):
+        try:
+            self.case_ids.remove(case_id)
+        except ValueError:
+            pass
+        if self.index >= len(self):
+            self.index = len(self) - 1
+
+    def seek(self, offset):
+        if self.inrange(offset):
+            self.index += offset
+        else:
+            raise IndexError
+
+    def cur(self):
+        try:
+            return self.case_ids[self.index]
+        except IndexError:
+            return None
+
+    def seek_case(self, case_id):
+        self.index = self.case_ids.index(case_id)
+
+    def append(self, case_id):
+        if not self.dynamic and case_id not in self.case_ids:
+            self.case_ids.append(case_id)
+            self.index = len(self.case_ids) - 1
+
+    def info(self):
+        return 'Record %d of %d' % (self.index + 1, len(self.case_ids))
+
+    def sort_by(self, *cols):
+        cur_case_id = self.cur()
+        query = globals.db.query('cases', order_by=cols)
+        query.join('JOIN persons USING (person_id)')
+        query.where_in('case_id', self.case_ids)
+        self.case_ids = query.fetchcols('case_id')
+        self.seek_case(cur_case_id)
+
+
+class CaseSet(CaseSetBase):
+
+    def __init__(self, case_ids=[], name=None):
+        self.case_ids = case_ids
+        if not name:
+            name = 'Unnamed'
+        self.name = name
+        self.index = 0
+
+
+class PersonCaseSet(CaseSetBase):
+
+    dynamic = True
+
+    def __init__(self, credentials, person_id, syndrome_id=None):
+        CaseSetBase.__init__(self)
+        self.person_id = person_id
+        self.syndrome_id = syndrome_id
+        self.load(credentials)
+
+    def getstate(self):
+        return self.syndrome_id, self.person_id
+
+    def setstate(self, state):
+        self.syndrome_id, self.person_id = state
+
+    def load(self, credentials):
+        query = globals.db.query('persons')
+        query.where('person_id = %s', self.person_id)
+        person = query.fetchone()
+        if person is None:
+            return
+        self.name = 'All cases for %s, %s' %\
+            (person.surname, person.given_names)
+        query = globals.db.query('cases', order_by='case_id')
+        caseaccess.acl_query(query, credentials, deleted=None)
+        query.where('person_id = %s', self.person_id)
+        if self.syndrome_id is not None:
+            query.where('syndrome_id = %s', self.syndrome_id)
+        self.case_ids = query.fetchcols('case_id')
+
+
+class ContactCaseSet(CaseSetBase):
+
+    dynamic = True
+
+    def __init__(self, credentials, case_id):
+        CaseSetBase.__init__(self)
+        self.case_id = case_id
+        self.load(credentials)
+
+    def getstate(self):
+        return self.case_id
+
+    def setstate(self, state):
+        self.case_id = state
+
+    def load(self, credentials):
+        query = globals.db.query('persons')
+        query.join('JOIN cases USING (person_id)')
+        query.where('case_id = %s', self.case_id)
+        person = query.fetchone()
+        if person is None:
+            return
+        self.name = '%ss of %s, %s (ID %s)' %\
+            (config.contact_label, person.surname, 
+             person.given_names, self.case_id)
+        query = globals.db.query('cases', order_by='case_id')
+        caseaccess.contact_query(query, self.case_id)
+        self.case_ids = query.fetchcols('case_id')
+
+
+class _CSInfo(object):
+
+    def __init__(self, caseset_id, name, dynamic):
+        self.caseset_id = caseset_id
+        self.name = name
+        self.dynamic = dynamic
+
+
+class SavedCasesetList(list, cached.Cached):
+
+    def __init__(self, cred):
+        self.cred = cred
+
+    def load(self):
+        query = globals.db.query('casesets', order_by='LOWER(name)')
+        subq = query.sub_expr('OR')
+        subq.where('user_id = %s', self.cred.user.user_id)
+        subq.where('unit_id = %s', self.cred.unit.unit_id)
+        subq.where('(unit_id IS NULL AND user_id IS NULL)')
+        cols = 'caseset_id', 'name', 'dynamic'
+        self[:] = [_CSInfo(*row) for row in query.fetchcols(cols)]
+
+
+class CaseSets(object):
+    """
+    Dummy at this stage - ultimately it should be backed by casesets tables
+
+    The intention is to implement two collections of case sets:
+
+     * A FIFO of "Recent" (transient) case sets
+
+     * A library of named case sets. These can be private, shared with your
+       role, or public.
+    """
+    NRECENT = 8
+    sort_options = [
+        ('onset_datetime:surname', 'Onset date'),
+        ('notification_datetime:surname', 'Notification date'),
+        ('surname:given_names:case_id', 'Surname'),
+        ('given_names:surname:case_id', 'Given names'),
+        ('case_id', 'ID'),
+        ('dob desc:surname', 'Age'),
+    ]
+
+    def __init__(self, cred):
+        self.cred = cred
+        self.saved_casesets = SavedCasesetList(self.cred)
+        self.recent_casesets = []
+        self.new_name = None
+
+    def save(self, cs):
+        row = None
+        if cs.caseset_id:
+            query = globals.db.query('casesets')
+            query.where('caseset_id = %s', cs.caseset_id)
+            row = query.fetchone()
+        if row is None:
+            row = globals.db.new_row('casesets')
+            row.user_id = self.cred.user.user_id
+        row.name = cs.name
+        row.dynamic = cs.dynamic
+        row.pickle = pickle.dumps(cs, -1)
+        row.db_update()
+        try:
+            self.recent_casesets.remove(cs)
+        except ValueError:
+            pass
+        cs.caseset_id = row.caseset_id
+        self.saved_casesets.cache_invalidate()
+
+    def load(self, id):
+        query = globals.db.query('casesets')
+        query.where('caseset_id = %s', id)
+        row = query.fetchone()
+        if row is not None:
+            cs = pickle.loads(str(row.pickle))
+            cs.caseset_id = id
+            return cs
+
+    def rename(self, cs, new_name):
+        if new_name:
+            cs.name = new_name
+            if cs.caseset_id is not None:
+                self.save(cs)
+
+    def delete(self, cs):
+        if cs.caseset_id is not None:
+            query = globals.db.query('casesets')
+            query.where('caseset_id = %s', cs.caseset_id)
+            query.delete()
+            cs.caseset_id = None
+            if cs:
+                self.add_recent(cs)
+            self.saved_casesets.cache_invalidate()
+
+    def add_recent(self, cs):
+        self.recent_casesets.insert(0, cs)
+        del self.recent_casesets[self.NRECENT:]
+
+    def use(self, credentials, cs):
+        cs.load(credentials)
+        if cs.caseset_id is None:
+            try:
+                self.recent_casesets.remove(cs)
+            except ValueError:
+                pass
+            self.add_recent(cs)
+        return cs
+
+    def actionoptions(self, cur_cs):
+        """
+        Return an optionexpr list of case set actions
+        """
+        self.saved_casesets.refresh()
+        options = [
+            ('', cur_cs.name),
+        ]
+        if cur_cs is not None:
+            options.append(('report', 'Open as report'))
+            if not cur_cs.dynamic:
+                options.append(('rename', 'Rename this case set'))
+            if cur_cs.caseset_id is None:
+                options.append(('save', 'Save this case set'))
+            else:
+                options.append(('delete', 'Delete case set'))
+            for sortcol, label in self.sort_options:
+                options.append(('sort:' + sortcol, 'Sort by ' + label))
+        for csi in self.saved_casesets:
+            if cur_cs is None or cur_cs.caseset_id != csi.caseset_id:
+                options.append(('load:%s' % csi.caseset_id, 
+                                'Load: ' + csi.name))
+        return options
+
+    def caseoptions(self, cur_cs):
+        self.saved_casesets.refresh()
+        options = [('caseset_add:', 'New case set')]
+        for csi in self.saved_casesets:
+            if not csi.dynamic and (cur_cs is None 
+                                 or cur_cs.caseset_id != csi.caseset_id):
+                name = csi.name
+                if len(name) > 30:
+                    name = '%s ...' % name[:40]
+                options.append(('caseset_add:%s' % csi.caseset_id, 
+                                'Add to: ' + name))
+        return options
diff --git a/casemgr/casestatus.py b/casemgr/casestatus.py
new file mode 100644
index 0000000..ac69dee
--- /dev/null
+++ b/casemgr/casestatus.py
@@ -0,0 +1,54 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# At some point we may reinstate/implement these features:
+#  * state transitions dependant on unit access rights
+#  * multiple states per case
+
+from casemgr import syndcategorical
+
+class SyndromeCaseStatesInfo(syndcategorical.SyndromeCategoricalInfo):
+
+    title = 'Case Status Values'
+    table = 'syndrome_case_status'
+    order_by = 'syndcs_id'
+    explicit_order = True
+    defaults = [
+        ('preliminary', 'Preliminary'),
+        ('confirmed', 'Confirmed'),
+        ('excluded', 'Excluded'),
+    ]
+
+
+class EditSyndromeCaseStates(SyndromeCaseStatesInfo, 
+                             syndcategorical.EditSyndromeCategorical):
+
+    pass
+
+
+class SyndromesCaseStates(SyndromeCaseStatesInfo,
+                          syndcategorical.SyndromeCategorical):
+
+    pass
+
+
+_syndromes_case_states = SyndromesCaseStates()
+get_syndrome = _syndromes_case_states.get_syndrome
+optionexpr = _syndromes_case_states.optionexpr
+get_label = _syndromes_case_states.get_label
+normalise_status = _syndromes_case_states.normalise
diff --git a/casemgr/casetags.py b/casemgr/casetags.py
new file mode 100644
index 0000000..fe6370d
--- /dev/null
+++ b/casemgr/casetags.py
@@ -0,0 +1,233 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import re
+
+from cocklebur.compat import *
+from cocklebur import utils
+from casemgr import globals, mergelabels, cached
+
+class InvalidTags(globals.Error): pass
+
+
+def edit_tag(tag_id):
+    row = None
+    if tag_id:
+        query = globals.db.query('tags')
+        query.where('tag_id = %s', int(tag_id))
+        row = query.fetchone()
+    if row is None:
+        row = globals.db.new_row('tags')
+    return row
+
+valid_tag_re = re.compile(r'^[a-zA-Z0-9_+-]+$')
+
+def check_tag(row):
+    if not valid_tag_re.match(str(row.tag)):
+        raise InvalidTags('Tag names can only contain upper and lower case letters, numbers, underscore (_) and hypen (-)')
+    tag = tags_from_str(row.tag)
+    if not tag:
+        raise InvalidTags('Tag must not be null')
+    if len(tag) > 1:
+        raise InvalidTags('Invalid tag name')
+    row.tag = row.tag.upper()
+
+
+class TagCacheItem(object):
+
+    cols = 'tag_id', 'tag', 'notes'
+
+    def __init__(self, row):
+        for col, value in zip(self.cols, row):
+            setattr(self, col, value)
+
+
+class TagCache(cached.NotifyCache, list):
+
+    notification_target = 'tags'
+
+    def __hash__(self):
+        return id(self)
+
+    def load(self):
+        query = globals.db.query('tags', order_by='tag')
+        self[:] = [TagCacheItem(row) 
+                   for row in query.fetchcols(TagCacheItem.cols)]
+        self.by_tag = {}
+        for tag in self:
+            self.by_tag[tag.tag.upper()] = tag
+
+tag_cache = TagCache()
+
+
+def tags():
+    tag_cache.refresh()
+    return tag_cache
+
+
+def notify():
+    """
+    Invalidate local and remote tag caches
+    """
+    tag_cache.cache_invalidate()
+    globals.notify.notify('tags')
+
+
+def use_count(tag_id):
+    """
+    Count cases associated with /tag_id/
+    """
+    query = globals.db.query('case_tags')
+    query.where('tag_id = %s', tag_id)
+    return query.aggregate('count(case_id)')
+
+
+def delete_tag(tag_id):
+    """
+    Delete the specified tag
+    """
+    assert tag_id
+    query = globals.db.query('tags')
+    query.where('tag_id = %s', tag_id)
+    query.delete()
+
+
+class Tags(set):
+    """
+    A set-derived collection of tags
+    """
+
+    def __str__(self):
+        return ' '.join(sorted(self))
+
+    def ids(self):
+        tag_cache.refresh()
+        return [tag_cache.by_tag[tag.upper()].tag_id for tag in self]
+
+    def validate(self):
+        tag_cache.refresh()
+        return [tag for tag in self if tag.upper() not in tag_cache.by_tag]
+
+splitre = re.compile(r'[\s/,]*')
+
+def tags_from_str(buf):
+    """
+    Return a Tags (set of tags) given a space or comma delimited string of tags
+    """
+    if buf is None:
+        return Tags()
+    if isinstance(buf, Tags):
+        return buf
+    tags = Tags()
+    for tag in splitre.split(buf.strip()):
+        if tag:
+            tags.add(tag.upper())
+    return tags
+
+
+def case_tags(case_id, for_update=False):
+    """
+    Return a Tags (set of tags) associated with the specified case
+    """
+    if not case_id:
+        return Tags()
+    query = globals.db.query('tags', for_update=for_update)
+    query.join('JOIN case_tags USING (tag_id)')
+    query.where('case_id = %s', case_id)
+    return Tags(query.fetchcols('tag'))
+
+
+class CasesTags(dict):
+    """
+    Efficiently load tags associated with multiple case ids,
+    producing a dict of Tags indexed by case id.
+    """
+
+    def __init__(self, case_ids):
+        query = globals.db.query('case_tags')
+        query.join('JOIN tags USING (tag_id)')
+        query.where_in('case_id', case_ids)
+        for case_id, tag in query.fetchcols(('case_id', 'tag')):
+            try:
+                tags = self[case_id]
+            except KeyError:
+                tags = self[case_id] = Tags()
+            tags.add(tag)
+
+
+def desc_changes(old, new):
+    desc = []
+    for del_tag in old - new:
+        desc.append('-' + del_tag)
+    for add_tag in new - old:
+        desc.append('+' + add_tag)
+    if desc:
+        return 'tags: ' + ' '.join(desc)
+
+
+def set_case_tags(case_id, tags):
+    tags = tags_from_str(tags)
+    bad_tags = tags.validate()
+    if bad_tags:
+        bad_tags.sort()
+        bad_tags = utils.commalist(bad_tags, 'and')
+        raise InvalidTags('Invalid tag(s): %s' % bad_tags)
+    cur_tags = case_tags(case_id, for_update=True)
+    del_tags = cur_tags - tags
+    if del_tags:
+        del_ids = del_tags.ids()
+        query = globals.db.query('case_tags')
+        query.where('case_id = %s', case_id)
+        query.where_in('tag_id', del_ids)
+        query.delete()
+    add_tags = tags - cur_tags
+    if add_tags:
+        for id in add_tags.ids():
+            row = globals.db.new_row('case_tags')
+            row.case_id = case_id
+            row.tag_id = id
+            row.db_update(refetch=False)
+    return desc_changes(cur_tags, tags)
+
+
+class CaseTags:
+    """
+    A helper for case editing - tracks "changed" status and can generate
+    a list of field edits (desc).
+    """
+
+    def __init__(self, case_id):
+        self.initial = self.cur = case_tags(case_id)
+
+    def normalise(self):
+        if not isinstance(self.cur, Tags):
+            self.cur = tags_from_str(self.cur)
+
+    def has_changed(self):
+        self.normalise()
+        return self.initial != self.cur
+
+    def update(self, case_id):
+        if self.has_changed():
+            set_case_tags(case_id, self.cur)
+            # If a rollback occurs, we will be out of sync...
+            self.initial = self.cur
+
+    def desc(self):
+        self.normalise()
+        return desc_changes(self.initial, self.cur)
diff --git a/casemgr/cmdline/README b/casemgr/cmdline/README
new file mode 100644
index 0000000..b8c42e8
--- /dev/null
+++ b/casemgr/cmdline/README
@@ -0,0 +1,35 @@
+Modules in this directory are NetEpi plug-ins that implement the netepi
+command line command.
+
+The script "app/cmdline.py" is installed into the system path, and is
+responsible for setting the sys.path so that application modules can
+be found. It then imports one of the "plug-ins" in this directory and
+calls the plug-in module's main() function with the (modified) cmdline
+arguments. The plug-in is then responsible for doing it's own setup and
+command line parsing.  In most cases, the plug-ins will be minimal,
+implemented as calls into the application modules, where the bulk of
+the logic will be.
+
+The driver script accepts (and suppresses) these arguments:
+
+    -D                  Enable application "debug" mode
+    -T                  Enable application "database trace" mode
+
+Modules must support these arguments:
+
+    -h, --help          Print module-specific usage
+
+Modules may support these arguments:
+
+    --csv               Write output in CSV format
+    -n, --dry-run       Do not perform any actions; show what would be
+                        performed
+    -o, --outfile       Write output to specified file
+    -q, --quiet         Run without generating unnecessary output
+    -s, --syndrome      Use specified syndrome/case definition (pattern)
+    -u, --username      Run with credentials of specified user (default to
+                        'admin').
+    -v, --verbose       Produce friendlier output (by default, output should
+                        suit scripting).
+
+Plug-ins should avoid using the above options for other purposes.
diff --git a/casemgr/cmdline/__init__.py b/casemgr/cmdline/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/casemgr/cmdline/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/casemgr/cmdline/cmdcommon.py b/casemgr/cmdline/cmdcommon.py
new file mode 100644
index 0000000..44026bc
--- /dev/null
+++ b/casemgr/cmdline/cmdcommon.py
@@ -0,0 +1,164 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import os
+
+from cocklebur import dbobj
+from casemgr import globals
+import config
+
+ourname = sys.argv[0]
+
+def abort(msg):
+    sys.exit('%s: %s' % (ourname, msg))
+
+
+def search_one(query, term, keycol, *cols):
+    assert len(cols) > 0
+    try:
+        return int(term)
+    except ValueError:
+        if len(cols) > 1:
+            likequery = query.sub_expr('OR')
+        else:
+            likequery = query
+        for col in cols:
+            likequery.where('%s ILIKE %%s' % col, dbobj.wild(term))
+        keys = query.fetchcols(keycol)
+        if not keys:
+            abort('%r not found' % term)
+        if len(keys) > 1:
+            labels = query.fetchcols(cols[0])
+            abort('%r matches multiple values: %s' % (term, ', '.join(labels)))
+        return keys[0]
+
+
+def get_syndrome_id(term):
+    if not term:
+        abort('No syndrome specified (see "%s list syndromes")' % ourname)
+    query = globals.db.query('syndrome_types')
+    query.where('enabled')
+    return search_one(query, term, 'syndrome_id', 'name')
+
+
+def get_user_id(term):
+    query = globals.db.query('users')
+    query.where('enabled')
+    query.where('not deleted')
+    return search_one(query, term, 'user_id', 'fullname', 'username')
+
+
+def get_unit_id(term):
+    query = globals.db.query('units')
+    query.where('enabled')
+    return search_one(query, term, 'unit_id', 'name')
+    
+
+def get_report_id(term):
+    query = globals.db.query('report_params')
+    query.where('sharing <> %s', 'last')
+    return search_one(query, term, 'report_params_id', 'label')
+
+
+def user_cred(options):
+    from casemgr import credentials
+    try:
+        cred = credentials.Credentials()
+        cred.auth_override(globals.db, options.username)
+    except credentials.SelectUnit:
+        # No mechanism to select appropriate units in this case at this time.
+        abort('user %r cannot be a member of multiple %ss' %
+                 (options.username, config.unit_label.lower()))
+    except credentials.CredentialError, e:
+        abort('authentication: %s: %r' % (e, options.username))
+    return cred
+    
+
+def opt_verbose(optp):
+    optp.add_option("-v", "--verbose", dest="verbose", action='store_true',
+            help="Emit additional status messages")
+
+def opt_user(optp):
+    optp.add_option('-u', '--username', default='admin',
+            help="Run with rights of user USERNAME (default '%default')", 
+            metavar="USERNAME")
+
+
+def opt_syndrome(optp):
+    optp.add_option('-s', '--syndrome',
+                    help="Use specified syndrome (%s)" % config.syndrome_label)
+
+
+def opt_outfile(optp):
+    optp.add_option("-o", "--outfile", dest="outfile",
+            help="Write output to FILENAME", metavar="FILENAME")
+
+
+class OUTFILE: pass
+
+
+def _list_replace(l, find, replace):
+    for i, v in enumerate(l):
+        if v is find:
+            l[i] = replace
+
+
+def _dict_replace(d, find, replace):
+    for k, v in d.iteritems():
+        if v is find:
+            d[k] = replace
+
+
+def safe_overwrite(options, fn, *args, **kw):
+    """
+    Note, this is not "safe" in the sense that tempfile means (against
+    people playing symlink tricks in the target directory. This only
+    attempts to ensure the target file is updated "atomically" (no
+    partial file) by renaming a temporary file into place. OUTFILE
+    should appear in the /fn/ arguments - it will be replaced with the
+    actual output file.
+    """
+    args = list(args)
+    if options.outfile:
+        outfile_dir, outfile_fn = os.path.split(options.outfile)
+        try:
+            tmp_fn = '%s.tmp%s' % (options.outfile, os.getpid())
+            outfile = open(tmp_fn, 'wb')
+        except EnvironmentError, (eno, estr):
+            abort('%s: %s: %s' % (ourname, options.outfile, estr))
+        try:
+            _list_replace(args, OUTFILE, outfile)
+            _dict_replace(kw, OUTFILE, outfile)
+            ret = fn(*args, **kw)
+        except Exception, e:
+            try:
+                outfile.close()
+            except EnvironmentError:
+                pass
+            os.unlink(tmp_fn)
+            abort(e)
+        else:
+            outfile.close()
+            os.rename(tmp_fn, options.outfile)
+    else:
+        _list_replace(args, OUTFILE, sys.stdout)
+        _dict_replace(kw, OUTFILE, sys.stdout)
+        ret = fn(*args, **kw)
+        sys.stdout.flush()
+    return ret
diff --git a/casemgr/cmdline/dupscan.py b/casemgr/cmdline/dupscan.py
new file mode 100644
index 0000000..bcce160
--- /dev/null
+++ b/casemgr/cmdline/dupscan.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+from optparse import OptionParser
+
+try:
+    import psyco
+except ImportError:
+    pass
+else:
+    psyco.full()
+
+import config
+from casemgr import globals, persondupe
+from casemgr.notification.client import connect as notify_connect
+from casemgr.cmdline import cmdcommon
+
+usage = '%prog [options]'
+
+def main(args):
+    optp = OptionParser(usage=usage)
+    cmdcommon.opt_user(optp)
+    cmdcommon.opt_verbose(optp)
+    options, args = optp.parse_args(args)
+
+    cred = cmdcommon.user_cred(options)
+
+    globals.notify = notify_connect(config.cgi_target,
+                                    config.notification_host,
+                                    config.notification_port)
+
+    try:
+        mp = persondupe.MatchPersons(globals.db, None)
+        mp.save(globals.db)
+        if options.verbose:
+            print mp.stats()
+            mp.report()
+        globals.db.commit()
+    except persondupe.DupeRunning:
+        cmdcommon.abort('Already running')
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/casemgr/cmdline/exportcases.py b/casemgr/cmdline/exportcases.py
new file mode 100644
index 0000000..33538df
--- /dev/null
+++ b/casemgr/cmdline/exportcases.py
@@ -0,0 +1,119 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+import fnmatch
+
+from optparse import OptionParser
+
+from casemgr import globals, credentials, exportselect
+from casemgr.syndrome import UnitSyndromesView
+from casemgr.cmdline import cmdcommon
+
+ourname = os.path.basename(sys.argv[0])
+
+def get_option_parser():
+    """
+    Create the option parser for the script
+    """
+    usage = '%prog [options] -s syndrome [forms] (use \\? for a list)'
+    optp = OptionParser(usage=usage)
+    cmdcommon.opt_user(optp)
+    cmdcommon.opt_outfile(optp)
+    cmdcommon.opt_syndrome(optp)
+    optp.set_defaults(export_scheme="classic", 
+                      deleted='n', strip_newlines=False)
+    optp.add_option("-x", "--exclude-deleted", dest="deleted",
+            action="store_const", const='n',
+            help="exclude deleted records from output")
+    optp.add_option("-d", "--include-deleted", dest="deleted",
+            action="store_const", const="both", 
+            help="include deleted records in output")
+    optp.add_option("--only-deleted", dest="deleted",
+            action="store_const", const='y',
+            help="include deleted records in output")
+    optp.add_option("-l", "--strip-newlines", dest="strip_newlines",
+            action="store_true",
+            help="replace newlines embedded in fields with spaces")
+    optp.add_option("-S", "--scheme", dest="export_scheme",
+            help="export using EXPORTSCHEME, default 'classic'. Use '\\?' "
+                 "to see a list of available schemes.", metavar="EXPORTSCHEME")
+    return optp
+
+def print_indexed_list(indexed_list):
+    maxlen = max([ len(str(i[0])) for i in indexed_list ])
+    indexed_list = [ (name, label) for (label, name) in indexed_list ]
+    indexed_list.sort()
+    fmt = "%%%ds: %%s" % maxlen
+    for name, label in indexed_list:
+        print fmt % (label, name)
+
+
+def print_export_schemes(es):
+    """
+    Print a human readable mapping of syndrome names to id
+    """
+
+
+def main(args):
+    """
+    Parse arguments and simulate the use of the export page
+    """
+
+    optp = get_option_parser()
+    options, args = optp.parse_args(args)
+
+
+    cred = cmdcommon.user_cred(options)
+
+    syndrome_id = cmdcommon.get_syndrome_id(options.syndrome)
+
+    es = exportselect.ExportSelect(syndrome_id)
+    es.deleted = options.deleted
+    es.strip_newlines = options.strip_newlines
+
+    # Parse export scheme specification
+    schemes = [scheme for scheme, description in es.scheme_options()]
+    if options.export_scheme == '?' or options.export_scheme not in schemes:
+        print 'Export schemes:'
+        print_indexed_list(list(es.scheme_options()))
+        sys.exit(1)
+    es.export_scheme = options.export_scheme
+
+    es.refresh(cred)
+    exporter = es.exporter
+
+    # Parse forms (if any)
+    if '?' in args:
+        print 'Forms:'
+        forms = [(form.label, form.name) for form in exporter.forms]
+        print_indexed_list(forms)
+        sys.exit(1)
+    formnames = [form.label for form in exporter.forms]
+    for name in args:
+        if name not in formnames:
+            optp.error('form %r not found (for this syndrome?)' % name)
+    include_forms = args
+
+    # load the data and export
+    cmdcommon.safe_overwrite(options, exporter.csv_write, 
+                             include_forms, cmdcommon.OUTFILE)
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
+
diff --git a/casemgr/cmdline/exportform.py b/casemgr/cmdline/exportform.py
new file mode 100644
index 0000000..4b55924
--- /dev/null
+++ b/casemgr/cmdline/exportform.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+from optparse import OptionParser
+
+from cocklebur.form_ui.xmlsave import xmlsave
+from casemgr import globals
+from casemgr.cmdline import cmdcommon
+
+def main(args):
+    optp = OptionParser()
+    cmdcommon.opt_outfile(optp)
+    options, args = optp.parse_args(args)
+    if len(args) == 2:
+        name = args[0]
+        version = int(args[1])
+    elif len(args) == 1:
+        name = args[0]
+        query = globals.db.query('forms')
+        query.where('label=%s', name)
+        row = query.fetchone()
+        if row is None:
+            cmdcommon.abort('Unknown form %r', name)
+        version = row.cur_version
+    else:
+        sys.exit('Usage: %s exportform <name> [version]')
+
+    try:
+        form = globals.formlib.load(name, version)
+    except Exception, e:
+        cmdcommon.abort(e)
+
+    cmdcommon.safe_overwrite(options, xmlsave, cmdcommon.OUTFILE, form)
diff --git a/casemgr/cmdline/exportreport.py b/casemgr/cmdline/exportreport.py
new file mode 100644
index 0000000..d662fb7
--- /dev/null
+++ b/casemgr/cmdline/exportreport.py
@@ -0,0 +1,43 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+
+from optparse import OptionParser
+
+from casemgr import globals
+from casemgr import reports
+
+from casemgr.cmdline import cmdcommon
+
+usage = '%prog [options] <report_name_or_pattern>'
+
+def main(args):
+    optp = OptionParser(usage=usage)
+    cmdcommon.opt_user(optp)
+    cmdcommon.opt_outfile(optp)
+    options, args = optp.parse_args(args)
+    if len(args) != 1:
+        optp.usage()
+
+    report_id = cmdcommon.get_report_id(args[0]) 
+
+    cred = cmdcommon.user_cred(options)
+
+    params = reports.load(report_id, cred)
+    cmdcommon.safe_overwrite(options, params.xmlsave, cmdcommon.OUTFILE)
diff --git a/casemgr/cmdline/geocodechangedaddresses.py b/casemgr/cmdline/geocodechangedaddresses.py
new file mode 100644
index 0000000..f58353e
--- /dev/null
+++ b/casemgr/cmdline/geocodechangedaddresses.py
@@ -0,0 +1,170 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os, csv, re
+from optparse import OptionParser
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from notifychangedfields import add_export_options, get_export_scheme_instance
+from notifychangedfields import add_cache_options, CacheFilter
+
+
+ourname = os.path.basename(sys.argv[0])
+
+configurable_options = {
+        'url': 'http://localhost/~febrl/submit_job.cgi',
+        'queue': 'general',
+        'priority': 100,
+        'password': 'halibut',
+        'neighbour_match_level': 2,
+        'email': None,
+        'id_field': None,
+        'target': None,
+        'address_fields': [],
+        'input_file': None,
+    }
+
+def get_option_parser():
+    """
+    Create the option parser for the script
+    """
+    usage = '%prog [options] <syndrome_id> <form_id> <form_id> ... (use ? for a list)'
+    parser = OptionParser(usage=usage)
+    add_export_options(parser)
+    add_cache_options(parser)
+    parser.set_defaults(**configurable_options)
+    parser.add_option("-i", "--input-file",
+            help="read csv input from FILENAME [default: %default]", metavar="FILENAME")
+    parser.add_option("--url",
+            help="connect to geocoder at URL [default: %default]", metavar="FILENAME")
+    parser.add_option("-q", "--queue",
+            help="submit jobs to QUEUE [default: %default]", metavar="QUEUE")
+    parser.add_option("-p", "--priority",
+            type="int",
+            help="submit jobs with priority INT [default: %default]", metavar="INT")
+    parser.add_option("-P", "--password",
+            help="password used to set higher priority geocoder jobs", metavar="PASSWORD")
+    parser.add_option("-N", "--neighbour-match-level",
+            type="int",
+            help="geocoder neighbour match level [default: %default]", metavar="INT")
+    parser.add_option("-e", "--email",
+            help="mail completed jobs to this address [default: %default]", metavar="ADDRESS")
+    parser.add_option("-I", "--id-field",
+            help="source id field [default: %default]", metavar="FIELD")
+    parser.add_option("-T", "--target",
+            help="target for geocoder results [default: %default]", metavar="TARGET")
+    parser.add_option("-A", "--address-field", dest="address_fields",
+            action="append",
+            help="target field and source components [default: %default]", metavar="TARGET,SOURCE[,SOURCE,...]")
+    return parser
+
+
+class DummyExporter:
+    FORM_ID_RE = re.compile(r"^(?P<form_name>.*?)\.form_id(\.\d+)$")
+
+    def __init__(self, csv_reader):
+        self.__reader = csv_reader
+        # we need to determine the forms in the file
+        # look for fields of the form name.form_id[.number]
+        self.header_row = self.__reader.next()
+        self.include_forms = []
+        for column in self.header_row:
+            m = self.FORM_ID_RE.match(column)
+            if m and m.group('form_name') not in self.include_forms:
+                self.include_forms.append(m.group('form_name'))
+
+    def row_gen(self):
+        yield self.header_row
+        for r in self.__reader:
+            yield r
+
+
+def main(args):
+    """
+    Parse arguments and simulate the use of the export page
+    """
+
+    parser = get_option_parser()
+    options, args = parser.parse_args(args)
+    if options.input_file:
+        if options.input_file == "-":
+            input = sys.stdin
+        else:
+            input = file(options.input_file)
+        es = DummyExporter(csv.reader(input))
+        cred = None
+    else:
+        es, cred = get_export_scheme_instance(parser, options, args)
+
+    # Some simple validity checks
+    if not options.address_fields:
+        parser.error('No composed address fields specified')
+    if not options.id_field:
+        parser.error('No id field specified')
+    if not options.target:
+        parser.error('No target specified')
+
+    # Build some list of which components constitute which target fields
+    monitored_fields = set()
+    address_components = {}
+    target_field = {}
+    for address_field in options.address_fields:
+        parts = address_field.split(',')
+        target = parts.pop(0)
+        if not parts:
+            parser.error('Address specifier must have a non-empty component list')
+        address_components[target] = parts
+        monitored_fields.update(parts)
+        for part in parts:
+            target_field[part] = target
+    options.monitored_fields = monitored_fields | set(options.monitored_fields)
+
+    cache_filter = CacheFilter(es, options)
+
+    # Generate geocoder data for changed data
+    input_addresses = []
+    for row, changed_values in cache_filter:
+        required_targets = set()
+        for changed_field, old_value, new_value in changed_values:
+            try:
+                required_targets.add(target_field[changed_field])
+            except KeyError:
+                # really shouldn't happen because why would we be monitoring
+                # a field and not using it?
+                pass
+        # we're going to misuse the geocoder id field slightly
+        # each row is going to be id,target_field<space>address
+        for target in required_targets:
+            id = "%s,%s" % (row[options.id_field], target)
+            address = " ".join([ row[f] for f in address_components[target] ])
+            input_addresses.append(" ".join([id, address]))
+
+    # Send any data to the geocoder
+    if not options.dry_run:
+        print "\n".join(input_addresses)
+
+    # Clean up
+    cache_filter.commit()
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
+
+# vim:ts=4:sw=4:et:ai
diff --git a/casemgr/cmdline/importxml.py b/casemgr/cmdline/importxml.py
new file mode 100644
index 0000000..e38b535
--- /dev/null
+++ b/casemgr/cmdline/importxml.py
@@ -0,0 +1,234 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import os
+import re
+import time
+import optparse
+
+try:
+    from lxml.etree import iterparse, dump
+except ImportError, e:
+    sys.exit('required python module "lxml" not found\n   see http://codespeak.net/lxml/\n    %s' % e)
+
+from cocklebur import dbobj
+from casemgr import cases, globals, syndrome, form_summary
+from casemgr.cmdline import cmdcommon
+
+class TagError(Exception): pass
+
+datasrc = 'lab_hl7'
+
+def copy_node(node, ns):
+    for tag in node.getchildren():
+        value = tag.text
+        if value:
+            value = value.strip()
+        if value and hasattr(ns, tag.tag):
+            setattr(ns, tag.tag, value)
+
+
+class FormDataImpCache(dict):
+
+    def get(self, syndrome_id, name, data_src):
+        key = syndrome_id, name
+        try:
+            return self[key]
+        except KeyError:
+            formdataimp = form_summary.FormDataImp(syndrome_id, name, data_src)
+            self[key] = formdataimp
+            return formdataimp
+            
+formdataimpcache = FormDataImpCache()
+
+
+class SyndromeCache(dict):
+
+    def __init__(self, default_syndrome=None):
+        if default_syndrome:
+            self[None] = self.find_syndrome(default_syndrome)
+
+    def find_syndrome(self, pattern):
+        try:
+            return self[pattern]
+        except KeyError:
+            if pattern is None:
+                cmdcommon.abort('No default syndrome specified')
+            patre = re.compile(pattern, re.I)
+            matches = [synd for synd in syndrome.syndromes
+                       if patre.match(synd.name) is not None]
+            if not matches:
+                cmdcommon.abort('No syndromes match %r' % pattern)
+            if len(matches) > 1:
+                names = [synd.name for synd in matches]
+                cmdcommon.abort('More than one syndrome name match %r: %s' % 
+                         (pattern, ', '.join(names)))
+            self[pattern] = matches[0]
+            return matches[0]
+
+
+class Ticker:
+
+    tick_interval = 10
+
+    def __init__(self, entity='records'):
+        self.t0 = time.time()
+        self.n = 0
+        self.entity = entity
+        self.next = 10
+
+    def report(self):
+        el = time.time() - self.t0
+        self.per_second = self.n / el
+        print 'Processed %d %s, %.1f per minute' %\
+            (self.n, self.entity, self.per_second * 60)
+        self.next = self.n + self.per_second * self.tick_interval
+
+    def tick(self):
+        self.n += 1
+        if self.n >= self.next:
+            self.report()
+
+    def __del__(self):
+        try:
+            self.report()
+        except Exception:
+            pass
+
+
+def assert_tag(node, name):
+    if node.tag != name:
+        raise TagError('Expected a <%s> tag, not <%s>' % (name, node.tag))
+
+def want_elem(node, name):
+    subnode = node.find(name)
+    if subnode is None:
+        raise TagError('No <%s> tag found' % (name))
+    return subnode
+
+
+def proc_forms(options, synd, case, case_elem):
+    forms_elem = want_elem(case_elem, 'Forms')
+    assert_tag(forms_elem, 'Forms')
+    for form_elem in forms_elem.getchildren():
+        assert_tag(form_elem, 'Form')
+        form_name = form_elem.get('name')
+        try:
+            formdataimp = formdataimpcache.get(synd.syndrome_id, form_name, 
+                                               options.data_src)
+        except form_summary.form_ui.FormError:
+            raise TagError('Can\'t find %r form for syndrome %d: %s' % (form_name, synd.syndrome_id, synd.name))
+        edit_form, form_data = formdataimp.edit(case.case_row.case_id)
+        copy_node(form_elem, form_data)
+        edit_form.update()
+
+
+def proc_cases(options, cred, cases):
+    """
+    Case-based import - single <Person> element contained within each <Case>
+    """
+    ticker = Ticker('cases')
+    for case_elem in cases:
+        try:
+            assert_tag(case_elem, 'Case')
+            person_elem = want_elem(case_elem, 'Person')
+            synd = find_syndrome(case_elem.get('syndrome'))
+            case = cases.new_case(cred, synd.syndrome_id)
+            copy_node(person_elem, case.person)
+            case.person.data_src = options.data_src
+            case.update()
+            proc_forms(options, synd, case, case_elem)
+        except Exception, e:
+            dump(case_elem)
+            raise
+            cmdcommon.abort(e)
+        ticker.tick()
+    globals.db.commit()
+
+
+def proc_persons(options, cred, persons):
+    """
+    Person-based import - one or more <Case> elements inside a <Cases>
+    element inside each <Person>
+    """
+    ticker = Ticker('cases')
+    for person_elem in persons:
+        try:
+            assert_tag(person_elem, 'Person')
+            cases_elem = want_elem(person_elem, 'Cases')
+            person_id = None
+            for case_elem in cases_elem.getchildren():
+                assert_tag(case_elem, 'Case')
+                synd = find_syndrome(case_elem.get('syndrome'))
+                case = cases.new_case(cred, synd.syndrome_id, 
+                                      use_person_id=person_id)
+                if person_id is None:
+                    copy_node(person_elem, case.person)
+                    case.person.data_src = options.data_src
+                case.update()
+                if person_id is None:
+                    person_id = case.case_row.person_id
+                proc_forms(options, synd, case, case_elem)
+                ticker.tick()
+        except Exception, e:
+            dump(person_elem)
+            raise
+            cmdcommon.abort(e)
+    globals.db.commit()
+
+
+def main(args):
+    global find_syndrome
+
+    optp = optparse.OptionParser(
+        usage='usage: %prog xmlimport [options] <xmlfile>')
+    cmdcommon.opt_syndrome(optp)
+    cmdcommon.opt_user(optp)
+    optp.add_option('-d', '--data-src', default='xmlimport',
+                    help='Set data source to DATA_SRC')
+    options, args = optp.parse_args(args)
+
+    try:
+        xmlfile, = args
+    except ValueError:
+        optp.error('exactly 1 argument needed')
+
+    cred = cmdcommon.user_cred(options)
+
+    find_syndrome = SyndromeCache(options.syndrome).find_syndrome
+
+    context = iter(iterparse(open(xmlfile), events=('start','end')))
+    event, root = context.next()
+
+    def yield_nodes(nodename):
+        for event, elem in context:
+            if event == 'end' and elem.tag == nodename:
+                yield elem
+                root.clear()
+
+    if root.tag == 'Cases':
+        proc_cases(options, cred, yield_nodes('Case'))
+    elif root.tag == 'Persons':
+        proc_persons(options, cred, yield_nodes('Person'))
+    else:
+        cmdcommon.abort('Root of XML tree is not a <Cases> tag nor <Persons> tag')
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/casemgr/cmdline/listforms.py b/casemgr/cmdline/listforms.py
new file mode 100644
index 0000000..404abe2
--- /dev/null
+++ b/casemgr/cmdline/listforms.py
@@ -0,0 +1,35 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from optparse import OptionParser
+
+from cocklebur import datetime
+from casemgr import globals
+from casemgr.cmdline import cmdcommon
+
+def main(args):
+    optp = OptionParser()
+    cmdcommon.opt_verbose(optp)
+    options, args = optp.parse_args(args)
+    query = globals.db.query('forms', order_by='label')
+    for row in query.fetchall():
+        if options.verbose:
+            print '%s (version %s, updated %s)' % (row.label, row.cur_version, 
+                                        datetime.relative(row.def_update_time))
+        else:
+            print row.label
diff --git a/casemgr/cmdline/listreports.py b/casemgr/cmdline/listreports.py
new file mode 100644
index 0000000..1311e31
--- /dev/null
+++ b/casemgr/cmdline/listreports.py
@@ -0,0 +1,116 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+
+from optparse import OptionParser
+
+from cocklebur import datetime
+from casemgr import globals
+
+from casemgr.cmdline import cmdcommon
+
+def main(args):
+    optp = OptionParser()
+    cmdcommon.opt_syndrome(optp)
+    cmdcommon.opt_verbose(optp)
+    optp.add_option('--type',
+            help='Restrict to reports of TYPE')
+    optp.add_option('--user',
+            help='Show USER reports')
+    optp.add_option('--unit',
+            help='Show UNIT reports')
+    optp.add_option('--all', action='store_true',
+            help='Show all reports (ignore "sharing")')
+    optp.add_option('--csv', action='store_true',
+            help='Use CSV format')
+    options, args = optp.parse_args(args)
+    if options.user and options.unit:
+        optp.error('Only specify one of --user and --unit')
+
+
+    query = globals.db.query('report_params', order_by='sharing,type,label')
+
+    if options.syndrome:
+        synd_id = cmdcommon.get_syndrome_id(options.syndrome)
+        query.where('syndrome_id = %s', synd_id)
+
+    if options.type:
+        query.where_in('type', options.type.split(','))
+    if options.user:
+        user_id = cmdcommon.get_user_id(options.user)
+        query.where('user_id = %s', user_id)
+        query.where_in('sharing', ('private', 'last'))
+    elif options.unit:
+        unit_id = cmdcommon.get_unit_id(options.unit)
+        query.where('unit_id = %s', unit_id)
+        query.where('sharing = %s', 'unit')
+    elif not options.all:
+        query.where_in('sharing', ('public', 'quick'))
+
+    rows = query.fetchall()
+    unit_ids = set([r.unit_id for r in rows])
+    user_ids = set([r.user_id for r in rows])
+    if unit_ids:
+        from casemgr.unituser import units
+        units.load(*unit_ids)
+    if user_ids:
+        from casemgr.unituser import users
+        users.load(*user_ids)
+    if options.csv:
+        import csv
+        writer = csv.writer(sys.stdout)
+        writer.writerow(['id','label','type','sharing','user','unit','syndrome'])
+        for row in rows:
+            cols = [row.report_params_id, row.label, row.type, row.sharing]
+            if row.user_id and len(user_ids) > 1:
+                try:
+                    cols.append(users[row.user_id].username)
+                except KeyError:
+                    cols.append(row.user_id)
+            else:
+                cols.append(None)
+            if row.unit_id and len(unit_ids) > 1:
+                try:
+                    cols.append(units[row.unit_id].name)
+                except KeyError:
+                    cols.append(row.unit_id)
+            else:
+                cols.append(None)
+            cols.append(row.syndrome_id)
+            writer.writerow(cols)
+    else:
+        for row in rows:
+            label = row.label or ''
+            extra = [str(row.type)]
+            if not options.syndrome:
+                extra.append('syndrome_id %s' % row.syndrome_id)
+            if options.all:
+                extra.append('sharing %s' % row.sharing)
+            if row.user_id and len(user_ids) > 1:
+                try:
+                    extra.append('user %s' % users[row.user_id].username)
+                except KeyError:
+                    extra.append('user_id %s' % row.user_id)
+            if row.unit_id and len(unit_ids) > 1:
+                try:
+                    extra.append('unit %s' % units[row.unit_id].name)
+                except KeyError:
+                    extra.append('unit_id %s' % row.unit_id)
+            if extra:
+                label += ' [%s]' % ', '.join(extra)
+            print '%s: %s' % (row.report_params_id, label)
diff --git a/casemgr/cmdline/listsyndromes.py b/casemgr/cmdline/listsyndromes.py
new file mode 100644
index 0000000..08e895f
--- /dev/null
+++ b/casemgr/cmdline/listsyndromes.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+
+from casemgr import syndrome
+
+
+def main(args):
+    for synd in syndrome.syndromes:
+        if not synd.active():
+            name = '(disabled) %s' % synd.name
+        else:
+            name = synd.name
+        print '%4d: %s' % (synd.syndrome_id, name)
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/casemgr/cmdline/notifychangedfields.py b/casemgr/cmdline/notifychangedfields.py
new file mode 100644
index 0000000..2a2b6a6
--- /dev/null
+++ b/casemgr/cmdline/notifychangedfields.py
@@ -0,0 +1,249 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+import fnmatch
+import shelve
+import cPickle as pickle
+
+from optparse import OptionParser, OptionGroup
+from UserDict import UserDict
+
+from casemgr import globals, exportselect
+from casemgr.syndrome import UnitSyndromesView
+from casemgr import tasks
+from casemgr.cmdline import cmdcommon
+
+
+ourname = os.path.basename(sys.argv[0])
+
+def get_option_parser():
+    """
+    Create the option parser for the script
+    """
+    usage = '%prog [options] <form_id> <form_id> ... (use ? for a list)'
+    optp = OptionParser(usage=usage)
+    cmdcommon.opt_user(optp)
+
+    optg = OptionGroup(optp, 'Queue options')
+    optg.add_option('--queue-field', metavar='FIELD',
+            help='field containing the destination task queue name')
+    optg.add_option('-Q', '--queue-mapping', metavar='VALUE::QUEUE',
+            help='a mapping from a queue field value to a task queue name')
+    optg.add_option('-T', '--default-queue', metavar='QUEUE',
+            help='the default task queue name') 
+    optp.add_option_group(optg)
+
+    optg = OptionGroup(optp, 'Cache options')
+    optg.add_option('-S', '--state-file', default=None, metavar='FILENAME',
+            help='keep state in FILENAME') 
+    optg.add_option('-m', '--monitor-field', dest='monitored_fields',
+            default=[], action='append', metavar='FIELD',
+            help='field to watch (may be specified multiple times)') 
+    optp.add_option_group(optg)
+
+    optg = OptionGroup(optp, 'Export options')
+    cmdcommon.opt_syndrome(optg)
+    optg.add_option('--exclude-deleted', dest='include_deleted',
+            action='store_false',
+            help='exclude deleted records from output (default)')
+    optg.add_option('--include-deleted', dest='include_deleted',
+            default=False, action='store_const', const='', 
+            help='include deleted records in output')
+    optg.add_option('--only-deleted', dest='include_deleted',
+            action='store_true',
+            help='include only deleted records in output')
+    optg.add_option('--scheme', dest='export_scheme',
+            default='classic', metavar='SCHEME',
+            help='export using SCHEME [default: %default]. Use "?" '
+                 'to see a list of available schemes.') 
+    optp.add_option_group(optg)
+
+    return optp
+
+
+def print_indexed_list(indexed_list, title=None):
+    if title:
+        print title
+    maxlen = max([ len(str(i[0])) for i in indexed_list ])
+    indexed_list = [ (name, label) for (label, name) in indexed_list ]
+    indexed_list.sort()
+    fmt = "%%%ds: %%s" % maxlen
+    for name, label in indexed_list:
+        print fmt % (label, name)
+
+
+def dictifying_filter_gen(gen):
+    """
+    Return the rows of a CSV sequence as dictionaries (where the first
+    row is the field names)
+    """
+    field_names = None
+    for row in gen:
+        if field_names is None:
+            field_names = row
+            continue
+        yield dict([(field_names[n], v) for n, v in enumerate(row)])
+
+
+def get_export_scheme_instance(cred, syndrome_id, optp, options, args):
+    """
+    Check syndrome id and form labels are valid and handle '?' to print
+    a list if required.  Return an export scheme object.
+    """
+    es = exportselect.ExportSelect(syndrome_id)
+    es.include_deleted = options.include_deleted
+
+    # Parse export scheme specification
+    schemes = [scheme for scheme, description in es.scheme_options()]
+    if options.export_scheme == '?' or options.export_scheme not in schemes:
+        print_indexed_list(list(es.scheme_options()), 'Export schemes:')
+        sys.exit(1)
+    es.export_scheme = options.export_scheme
+
+    es.refresh(cred)
+    exporter = es.exporter
+
+    # Parse forms (if any)
+    if '?' in args:
+        forms = [(form.label, form.name) for form in exporter.forms]
+        print_indexed_list(forms, 'Forms:')
+        sys.exit(1)
+    formnames = [form.label for form in exporter.forms]
+    for name in args:
+        if name not in formnames:
+            optp.error('form %r not found (for this syndrome?)' % name)
+    include_forms = args
+
+    es.include_forms = include_forms
+    return es, cred
+
+
+
+class CacheFilter:
+    """
+    A class which takes an exporter and returns records which have
+    changed by comparing them against cached data
+    """
+    def __init__(self, export_scheme, options):
+        self.export_scheme = export_scheme
+        self.options = options
+        # Load our cached state if given
+        if not options.state_file:
+            # fake being a shelf
+            self.cached_state = UserDict()
+            self.cached_state.close = lambda: None
+        else:
+            self.cached_state = shelve.open(options.state_file)
+
+    def extract_row_key(self, row):
+        """
+        Extract a tuple from a row to uniquely identify it
+        """
+        key = [row['case_id'], row['syndrome_id']]
+        for form in self.export_scheme.include_forms:
+            base = '%s.form_id' % form
+            if base in row:
+                key.append(row[base])
+            else:
+                n = 0
+                while '%s.%d' % (base, n) in row:
+                    key.append(row['%s.%d' % (base, n)])
+                    n += 1
+        return tuple(key)
+
+    def __iter__(self):
+        # Run over value tuples checking against cached state
+        for row in dictifying_filter_gen(self.export_scheme.row_gen()):
+            row_key = self.extract_row_key(row)
+            row_key_repr = repr(row_key)
+            current_values = [ row[f] for f in self.options.monitored_fields ]
+            cached_values = self.cached_state.get(row_key_repr, [None] * len(self.options.monitored_fields))
+            changed_values = [ (f, cached_values[n], row[f]) for n, f in enumerate(self.options.monitored_fields) if row[f] != cached_values[n] ]
+            if changed_values:
+                #print row_key, '->', changed_values
+                yield (row, changed_values)
+
+    def update_cache(self, row):
+        row_key = self.extract_row_key(row)
+        row_key_repr = repr(row_key)
+        current_values = [ row[f] for f in self.options.monitored_fields ]
+        if not self.options.dry_run:
+            self.cached_state[row_key_repr] = current_values
+
+    def commit(self):
+        # Clean up
+        self.cached_state.close()
+
+
+def main(args):
+    """
+    Parse arguments and simulate the use of the export page
+    """
+
+    optp = get_option_parser()
+    options, args = optp.parse_args(args)
+
+    cred = cmdcommon.user_cred(options)
+
+    syndrome_id = cmdcommon.get_syndrome_id(options.syndrome)
+
+    es = get_export_scheme_instance(cred, syndrome_id, optp, options, args)
+
+    # Some simple validity checks
+    if not options.queue_field and not options.default_queue:
+        optp.error('No queue field specified')
+    if not options.monitored_fields:
+        optp.error('No fields specified to monitor')
+
+    cache_filter = CacheFilter(es, options)
+
+    # Generate tasks for changed cases
+    tasks.workqueues.load()
+    queue_ids = {}
+    for q in tasks.workqueues:
+        queue_ids[q.name] = q.queue_id
+    for row, changed_values in cache_filter:
+        case_id = row['case_id']
+        task = tasks.EditTask(globals.db, cred, case_id=case_id)
+        # either queue_field or default_queue must be set
+        if options.queue_field:
+            queue_name = row[options.queue_field]
+            if options.queue_mapping:
+                queue_name = options.queue_mapping[queue_name]
+            if options.default_queue and not queue_name:
+                queue_name = options.default_queue
+        else:
+            queue_name = options.default_queue
+        task.set_queue_id(globals.db, queue_ids[queue_name])
+        task.action = tasks.ACTION_UPDATE_CASE
+        task.task_description = "Case %s has changed fields: %s" % (case_id, ", ".join([v[0] for v in changed_values]))
+        task.update(globals.db)
+        cache_filter.update_cache(row)
+
+    # Commit the changes to the database
+    if not options.dry_run:
+        globals.db.commit()
+
+    # Clean up
+    cache_filter.commit()
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
+
+# vim:ts=4:sw=4:et:ai
diff --git a/casemgr/cmdline/notifymon.py b/casemgr/cmdline/notifymon.py
new file mode 100644
index 0000000..0e761a1
--- /dev/null
+++ b/casemgr/cmdline/notifymon.py
@@ -0,0 +1,57 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import sys
+import time
+import socket
+import config
+import errno
+
+def main(args):
+    if config.notification_host == 'none':
+        sys.exit('Notifications not enabled?')
+    if config.notification_host == 'local':
+        af = socket.AF_UNIX
+        address = os.path.join(config.cgi_target,'db','notification','socket')
+    else:
+        af = socket.AF_INET
+        address = config.notification_host, config.notification_port
+    while 1:
+        sock = socket.socket(af, socket.SOCK_STREAM)
+        try:
+            sock.connect(address)
+        except socket.error, (eno, estr):
+            if eno not in (errno.ECONNREFUSED, errno.ENOENT):
+                raise
+            time.sleep(1)
+            continue
+        print 'Connected'
+        sock.send('monitor on\n')
+        while 1:
+            try:
+                buf = sock.recv(8192)
+            except KeyboardInterrupt:
+                sys.exit(0)
+            if not buf:
+                break
+            sys.stdout.write(buf)
+        print 'Disconnected'
+
+if __name__ == '__main__':
+    main()
diff --git a/casemgr/cmdline/personindex.py b/casemgr/cmdline/personindex.py
new file mode 100644
index 0000000..4ac6e78
--- /dev/null
+++ b/casemgr/cmdline/personindex.py
@@ -0,0 +1,42 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+from casemgr import fuzzyperson
+from casemgr import globals
+
+def main(args):
+    curs = globals.db.cursor()
+    try:
+        curs.execute('SELECT person_id, surname, given_names FROM persons')
+        rows = curs.fetchall()
+    finally:
+        curs.close()
+    last_pc = 0
+    rowcnt = len(rows)
+    for i, (person_id, surname, given_names) in enumerate(rows):
+        pc = i * 100 / rowcnt
+        if last_pc != pc:
+            sys.stdout.write(' %d of %d %2d%% done\r' % (i+1, rowcnt, pc))
+            sys.stdout.flush()
+            last_pc = pc
+        fuzzyperson.update(globals.db, person_id, surname, given_names)
+    print ' %d of %d 100%% done' % (i+1, rowcnt)
+    globals.db.commit()
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/casemgr/cmdline/report.py b/casemgr/cmdline/report.py
new file mode 100644
index 0000000..2fb1db5
--- /dev/null
+++ b/casemgr/cmdline/report.py
@@ -0,0 +1,108 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import csv
+
+from optparse import OptionParser
+
+from casemgr import reports, messages
+from casemgr.cmdline import cmdcommon
+
+usage = '%prog [options] [report_name_or_pattern]'
+
+def html_table(options, cred, params):
+    cmdcommon.abort('HTML report not supported (yet)')
+
+
+def csv_table(options, cred, params):
+    try:
+        params.export_rows
+    except AttributeError:
+        cmdcommon.abort('report type %r does not support CSV export' % 
+                        params.report_type)
+    msgs = params.check()
+    if msgs.have_errors():
+        cmdcommon.abort('\n'.join(msgs.get_messages()))
+    try:
+        repgen = params.export_rows(cred)
+    except reports.Error, e:
+        cmdcommon.abort(e)
+    csv_write = lambda f, lines: csv.writer(f).writerows(lines)
+    cmdcommon.safe_overwrite(options, csv_write, cmdcommon.OUTFILE, repgen)
+
+
+def image_out(cred, params, outfile):
+    msgs = params.check()
+    try:
+        params.report(cred, msgs, filename=outfile)
+    except reports.Error, e:
+        cmdcommon.abort(e)
+
+
+def main(args):
+    optp = OptionParser(usage=usage)
+    cmdcommon.opt_user(optp)
+    cmdcommon.opt_outfile(optp)
+    cmdcommon.opt_syndrome(optp)
+    optp.add_option('--csv', action='store_true',
+            help='For line report, use CSV format (default)')
+    optp.add_option('--html', action='store_true',
+            help='For line report, use HTML format')
+    optp.add_option('-p', '--param-file', metavar='FILENAME',
+                    help='Read report parameters from FILENAME')
+    options, args = optp.parse_args(args)
+    if options.param_file and args:
+        optp.error('Specify a parameter file OR report name, not both')
+
+    cred = cmdcommon.user_cred(options)
+
+    if args:
+        if len(args) > 1:
+            optp.error('Specify only one report name')
+        report_id = cmdcommon.get_report_id(args[0]) 
+        params = reports.load(report_id, cred)
+    elif options.param_file:
+        if not options.syndrome:
+            optp.error('Specify a syndrome')
+        syndrome_id = cmdcommon.get_syndrome_id(options.syndrome)
+        try:
+            f = open(options.param_file, 'rU')
+        except EnvironmentError, (eno, estr):
+            cmdcommon.abort('%s: %s' % (options.param_file, estr))
+        try:
+            try:
+                params = reports.parse_file(syndrome_id, f)
+            finally:
+                f.close()
+        except reports.Error, e:
+            cmdcommon.abort('%s: %s' % (options.param_file, e))
+    else:
+        optp.error('Specify either a parameter file OR report name')
+
+    if params.report_type in ('line', 'crosstab'):
+        if options.html:
+            html_table(options, cred, params)
+        else:
+            csv_table(options, cred, params)
+    elif params.report_type in ('epicurve', 'contactvis'):
+        if not options.outfile:
+            optp.error('Specify an output file (--outfile)')
+        image_out(cred, params, options.outfile)
+    else:
+        cmdcommon.abort('Report type %r not supported by command line interface' % params.report_type)
diff --git a/casemgr/contacts.py b/casemgr/contacts.py
new file mode 100644
index 0000000..523a915
--- /dev/null
+++ b/casemgr/contacts.py
@@ -0,0 +1,324 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import datetime, utils, dbobj
+from casemgr import globals, paged_search, person, search, casestatus, syndrome
+
+def contact_type_count_query():
+    return query
+
+class ContactTypeEdit:
+
+    def __init__(self, contact_type_id):
+        self.contact_type_id = contact_type_id
+        row = self._fetch()
+        self.name = row.contact_type
+        self.new_name = self.name
+
+    def _fetch(self, contact_type_id=None, for_update=False):
+        if contact_type_id is None:
+            contact_type_id = self.contact_type_id
+        query = globals.db.query('contact_types', for_update=for_update)
+        query.where('contact_type_id = %s', contact_type_id)
+        return query.fetchone()
+
+    def refresh(self):
+        query = globals.db.query('case_contacts')
+        query.where('contact_type_id = %s', self.contact_type_id)
+        query.where('case_id < contact_id')
+        self.count = query.aggregate('count(*)')
+        query = globals.db.query('contact_types', 
+                                 order_by='lower(contact_type)')
+        self.contact_types = query.fetchcols(('contact_type_id', 'contact_type'))
+        self.contact_type_map = dict(self.contact_types)
+
+    def type_label(self, id):
+        return self.contact_type_map.get(int(id), '???')
+
+    def merge_types(self):
+        types =  [(id, name) for id, name in self.contact_types
+                  if id != self.contact_type_id]
+        types.insert(0, ('', '(select a contact type)'))
+        return types
+
+    def rename(self):
+        if self.new_name and self.new_name.strip():
+            row = self._fetch(for_update=True)
+            row.contact_type = self.new_name.strip()
+            row.db_update()
+
+    def merge(self):
+        this_row = self._fetch(for_update=True)
+        other_row = self._fetch(self.merge_to_id, for_update=True)
+        if (this_row is None or other_row is None or 
+            this_row.contact_type_id == other_row.contact_type_id):
+            raise globals.Error('Unable to merge')
+        curs = globals.db.cursor()
+        try:
+            dbobj.execute(curs,
+                          'UPDATE case_contacts SET contact_type_id=%s'
+                          ' WHERE contact_type_id=%s', 
+                          (other_row.contact_type_id, this_row.contact_type_id))
+            self.count = curs.rowcount / 2
+        finally:
+            curs.close()
+        this_row.db_delete()
+
+    def delete(self):
+        this_row = self._fetch(for_update=True)
+        curs = globals.db.cursor()
+        try:
+            dbobj.execute(curs, 
+                        'DELETE FROM case_contacts WHERE contact_type_id=%s', 
+                         (this_row.contact_type_id,))
+            self.count = curs.rowcount / 2
+        finally:
+            curs.close()
+        this_row.db_delete()
+
+
+class ContactTypeAdmin:
+
+    cols = 'contact_type_id', 'contact_type', 'count'
+
+    def __init__(self, row):
+        for col, value in zip(self.cols, row):
+            setattr(self, col, value)
+
+        
+class ContactTypesAdmin(list):
+
+    def __init__(self):
+        cols = ('contact_types.contact_type_id', 'contact_type',
+                '(select count(*) from case_contacts'
+                ' where case_contacts.contact_type_id'
+                '  = contact_types.contact_type_id'
+                ' and case_id > contact_id)')
+        query = globals.db.query('contact_types',order_by='lower(contact_type)')
+        for row in query.fetchcols(cols):
+            self.append(ContactTypeAdmin(row))
+
+
+def new_contact_type(contact_type):
+    if contact_type:
+        row = globals.db.new_row('contact_types')
+        row.contact_type = contact_type
+        row.db_update()
+        return row.contact_type_id
+
+
+def dissociate_contact(id_a, id_b):
+    query = globals.db.query('case_contacts')
+    query.where('(case_id = %s AND contact_id = %s)', id_a, id_b)
+    query.delete()
+    query = globals.db.query('case_contacts')
+    query.where('(contact_id = %s AND case_id = %s)', id_a, id_b)
+    query.delete()
+
+
+def dissociate_contacts(case_id, contacts):
+    query = globals.db.query('case_contacts')
+    query.where('case_id = %s', case_id)
+    query.where_in('contact_id', contacts)
+    query.delete()
+    query = globals.db.query('case_contacts')
+    query.where('contact_id = %s', case_id)
+    query.where_in('case_id', contacts)
+    query.delete()
+
+
+def associate_contact(id_a, id_b, contact_type_id, contact_date):
+    def _update(case_id, contact_id, contact_type_id, contact_date):
+        query = globals.db.query('case_contacts', for_update=True)
+        query.where('(case_id = %s AND contact_id = %s)', case_id, contact_id)
+        row = query.fetchone()
+        if row is None:
+            row = globals.db.new_row('case_contacts')
+        row.case_id = case_id
+        row.contact_id = contact_id
+        row.contact_type_id = contact_type_id
+        row.contact_date = contact_date
+        row.db_update(refetch=False)
+
+    if id_a == id_b:
+        return
+    _update(id_a, id_b, contact_type_id, contact_date)
+    _update(id_b, id_a, contact_type_id, contact_date)
+
+
+class CaseContact:
+    cols = (
+        'person_id', 'cases.case_id', 'syndrome_id', 'case_status', 
+        'onset_datetime', 'deleted', 'contact_date', 'contact_type',
+    )
+
+    def __init__(self, case_row, person_loader):
+        for col, value in zip(self.cols, case_row):
+            col = col.split('.')[-1]
+            setattr(self, col, value)
+        self.person = person_loader.get(self.person_id)
+        self.contact_date = datetime.mx_parse_datetime(self.contact_date)
+        if self.contact_type is None:
+            self.contact_type = 'Unknown'
+        self.case_status = casestatus.get_label(self.syndrome_id, 
+                                                self.case_status)
+        self.syndrome_name = syndrome.syndromes[self.syndrome_id].name
+
+
+
+class ContactRowsMixin:
+
+    def contact_rows(self, case_ids):
+        query = globals.db.query('cases')
+        query.join('LEFT JOIN case_contacts ON (contact_id = %s'
+                    ' AND cases.case_id=case_contacts.case_id)', self.case_id)
+        query.join('LEFT JOIN contact_types USING (contact_type_id)')
+        query.where_in('cases.case_id', case_ids)
+        case_rows = {}
+        person_loader = person.DelayLoadPersons()
+        for row in query.fetchcols(CaseContact.cols):
+            case_row = CaseContact(row, person_loader)
+            case_rows[case_row.case_id] = case_row
+        not_found = person_loader.load(globals.db)
+        page_rows = []
+        for case_id in case_ids:
+            try:
+                page_rows.append(case_rows[case_id])
+            except KeyError:
+                pass
+        return page_rows
+
+
+class MostCommon(dict):
+
+    def add(self, value):
+        self[value] = self.get(value, 0) + 1  
+
+    def most_common(self):
+        if self:
+            items = [(count, value) for value, count in self.iteritems()]
+            items.sort()
+            return items[-1][1]
+
+
+class AssocContacts(paged_search.PagedSearch, ContactRowsMixin): 
+
+    def __init__(self, prefs, case_id, assoc_ids):
+        paged_search.PagedSearch.__init__(self, globals.db, prefs, None)
+        self.case_id = case_id
+        self.pkeys = assoc_ids
+        self.contact_type = None
+        self.datetime_fmt = datetime.mx_parse_datetime.format
+        self.guess_type_and_date()
+
+    def guess_type_and_date(self):
+        ct = MostCommon()
+        cd = MostCommon()
+        query = globals.db.query('case_contacts')
+        query.where('contact_id = %s', self.case_id)
+        query.where_in('case_id', self.pkeys)
+        cols = 'contact_type_id', 'contact_date'
+        for contact_type_id, contact_date in query.fetchcols(cols):
+            ct.add(contact_type_id)
+            cd.add(contact_date)
+        self.contact_type_id = ct.most_common()
+        self.contact_date = cd.most_common()
+        if self.contact_date is not None:
+            self.contact_date = self.contact_date.strftime(self.datetime_fmt)
+
+    def contact_types(self):
+        query = globals.db.query('contact_types', 
+                                 order_by='lower(contact_type)')
+        return query.fetchcols(('contact_type_id', 'contact_type'))
+
+    def new_contact_type(self, contact_type):
+        self.contact_type_id = new_contact_type(contact_type)
+
+    def page_rows(self):
+        return self.contact_rows(self.page_pkeys())
+
+    def associate(self):
+        for contact_id in self.pkeys:
+            associate_contact(self.case_id, contact_id, 
+                              self.contact_type_id, self.contact_date)
+
+    def log_msg(self):
+        return 'Associated contact ID(s) %s' % utils.commalist(self.pkeys, 'and')
+        
+
+
+class ContactSearch(paged_search.SortablePagedSearch, 
+                    paged_search.PagerSelect,
+                    search.SearchOrderMixin,
+                    ContactRowsMixin):
+
+    syndrome_id = None
+
+# Wont work - contact_type is not a demogfield
+#    orders = SearchOrderMixin.orders + [
+#        'contact_type,surname,given_names',
+#    ]
+
+    def __init__(self, prefs, case_id, deleted):
+        self.deleted = deleted
+        self.case_id = case_id
+        self.order_by = self.default_order
+        paged_search.PagerSelect.__init__(self)
+        paged_search.SortablePagedSearch.__init__(self, globals.db, prefs)
+
+    def new_search(self):
+        paged_search.SortablePagedSearch.new_search(self)
+        self.query = globals.db.query('cases', order_by=self.order_by)
+        self.query.join('JOIN case_contacts USING (case_id)')
+        self.query.join('JOIN persons USING (person_id)')
+        self.query.join('LEFT JOIN contact_types USING (contact_type_id)')
+        self.query.where('contact_id = %s', self.case_id)
+        if not self.deleted:
+            self.query.where('NOT deleted')
+
+    def fetch_pkeys(self, query):
+        paged_search.SortablePagedSearch.fetch_pkeys(self, query)
+        self.case_ids = set([keys[0] for keys in self.pkeys])
+
+    def __len__(self):
+        if self.pkeys is None:
+            self.fetch_pkeys(self.query)
+        return len(self.pkeys)
+
+    def page_rows(self):
+        if self.pkeys is None:
+            self.fetch_pkeys(self.query)
+        self.order = self.order_by_cols()
+        case_ids = [keys[0] for keys in self.page_pkeys()]
+        return self.contact_rows(case_ids)
+
+    def is_contact(self, case_id):
+        return case_id in self.case_ids
+
+    def selected_case_ids(self):
+        return [k[0] for k in self.selected]
+
+#    def get_demog_fields(self):
+#        # Multiple contact syndromes could be involved - just return the common
+#        return demogfields.get_demog_fields(globals.db, None)
+
diff --git a/casemgr/credentials.py b/casemgr/credentials.py
new file mode 100644
index 0000000..534bb6e
--- /dev/null
+++ b/casemgr/credentials.py
@@ -0,0 +1,418 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+The "credentials" object combines user authentication, access rights,
+preferences and logging functional (yes, it's grown somewhat beyond what it's
+name suggests).
+
+Note that we defer importing "globals" until it's needed, as parts of this code
+are used by the initial installer, and globals is not valid in that context.
+"""
+
+# Standard Python Libs
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+# 3rd Party
+from mx import DateTime
+
+# Application
+import config
+import notify
+from cocklebur import dbobj, utils
+from casemgr.preferences import Preferences
+from casemgr.rights import Rights
+from casemgr.unituser import units, users, null_unit, null_user
+from casemgr import pwcrypt
+
+class CredentialError(Exception):
+    pass
+
+class DisabledUser(CredentialError):
+    def __init__(self, msg, user=None):
+        CredentialError.__init__(self, msg)
+        self.user = user
+
+class TooManyAttempts(CredentialError):
+    pass
+
+class PasswordError(CredentialError):
+    pass
+
+class UsernameError(CredentialError):
+    pass
+
+class SelectUnit(Exception):
+    pass
+
+pwerror = PasswordError('Invalid username or incorrect password')
+
+privacy_reminder = (
+    'You have not set your privacy preferences - your contact details will '
+    'not be visible to other users of this system. If you wish to share '
+    'your contact details with other users, go to Tools->Preferences->Privacy'
+)
+
+details_reminder = (
+    'You have not checked your contact details in the last %s days. Please '
+    'take a moment to review them under Tools->User->Details and make '
+    'any corrections necessary.'
+) % config.user_check_interval
+
+
+max_bad_passwords = 5
+bad_time_penalty_mins = 5
+
+
+def pwd_check(user, password):
+    if not password:
+        raise PasswordError('Please enter your password')
+    if not pwcrypt.pwd_check(user.password, password):
+        raise PasswordError('Incorrect password')
+
+valid_user_re = re.compile(r'^[a-z][a-z0-9_.-]{2,}$', re.IGNORECASE)
+
+def valid_username(user):
+    if not user.username or not valid_user_re.match(user.username):
+        raise UsernameError(
+            'Usernames must be at least 3 characters long, can only contain '
+            'upper or lower case letters, numbers, period, underscores and '
+            'hypens, and must start with a letter')
+
+def valid_fullname(user):
+    if not user.fullname or len(user.fullname.strip()) < 3:
+        raise CredentialError('A full name (greater than 3 characters) must be '
+                              'supplied.')
+
+def valid_contact(user):
+    for attr in ('phone_home', 'phone_work', 'phone_mobile', 'email'):
+        value = getattr(user, attr)
+        if value and len(value.strip()) >= 3:
+            break
+    else:
+        raise CredentialError('Either home phone, work phone, mobile phone or '
+                              'e-mail address must be supplied')
+    if user.email:
+        try:
+            parts = utils.parse_addr(user.email)
+        except ValueError, e:
+            raise CredentialError(str(e))
+        user.email = '%s@%s' % parts
+
+
+def get_user_units(db, user_id, cols='unit_id'):
+    query = db.query('units', distinct = True)
+    query.where('units.enabled = True')
+    query.join('LEFT JOIN unit_users USING (unit_id)')
+    query.where('unit_users.unit_id = units.unit_id')
+    query.where('unit_users.user_id = %s', user_id)
+    return query.fetchcols(cols)
+
+
+def logger(db, table, user_id, event_type, **kwargs):
+    from casemgr import globals
+    if not event_type:
+        return
+    log = db.new_row(table)
+    log.user_id = user_id
+    log.event_type = event_type
+    log.remote_addr = globals.remote_host
+    for k, v in kwargs.items():
+        if v is not None:
+            setattr(log, k, v)
+    log.db_update(refetch=False)
+
+def user_log(db, user_id, event_type, **kw):
+    logger(db, 'user_log', user_id, event_type, **kw)
+
+def admin_log(db, user_id, event_type, **kw):
+    logger(db, 'admin_log', user_id, event_type, **kw)
+
+def timelock_remain(user):
+    now = DateTime.now()
+    five_minutes = DateTime.DateTimeDelta(0,0,bad_time_penalty_mins)
+    if (user.bad_attempts >= max_bad_passwords 
+        and user.bad_timestamp + five_minutes > now):
+        return (user.bad_timestamp + five_minutes) - now
+
+def timelock_remain_str(user):
+    remain = timelock_remain(user)
+    if remain:
+        if remain.hour > 1:
+            return '%d hours' % round(remain.hours)
+        if remain.minute > 1:
+            return '%d minutes' % round(remain.minutes)
+        else:
+            return '%d seconds' % round(remain.seconds)
+    return ''
+
+
+def delete_user(user):
+    if user.user_id is None:
+        return                  # New user, not saved
+    if user.enabled:
+        raise CredentialError('Disable user before deleting')
+    if user.deleted:
+        raise CredentialError('User is already deleted?')
+    user.deleted = True
+    isodate = DateTime.now().strftime('%Y-%m-%d %H:%M:%S')
+    user.username = '%s DELETED %s' % (user.username, isodate)
+    user.db_update()
+
+
+def undelete_user(user):
+    if not user.deleted:
+        raise CredentialError('User is not deleted')
+    user.deleted = False
+    n = user.username.find(' DELETED')
+    if n >= 0:
+        user.username = user.username[:n]
+    user.db_update()
+
+
+def delete_unit(unit):
+    if unit.unit_id is None:
+        return                  # New unit, not saved
+    if unit.enabled:
+        raise CredentialError('Disable unit before deleting')
+    query = unit.db().query('users')
+    query.join('JOIN unit_users USING (user_id)')
+    query.where('unit_users.unit_id = %s', unit.unit_id)
+    query.where('not users.deleted')
+    users = query.fetchcols('username')
+    if users:
+        raise CredentialError(config.unit_label + ' is associated with users'
+                              ' and cannot be deleted')
+    query = unit.db().query('units')
+    query.where('unit_id = %s', unit.unit_id)
+    try:
+        query.delete()
+    except dbobj.ConstraintError:
+        raise CredentialError(config.unit_label + ' is referenced by another entity')
+
+
+def group_units(db, group_id):
+    """
+    Return a list of units that reference the specified group_id
+    """
+    query = db.query('unit_groups')
+    query.where('group_id = %s', group_id)
+    return query.fetchcols('unit_id')
+
+
+class Credentials(object):
+    def __init__(self):
+        self.clear()
+
+    def __getstate__(self):
+        state = self.__dict__.copy()
+        state['user_id'] = state.pop('user').user_id
+        state['unit_id'] = state.pop('unit').unit_id
+        return state
+
+    def __setstate__(self, state):
+        state['user'] = users[state.pop('user_id')]
+        state['unit'] = units[state.pop('unit_id')]
+        self.__dict__.update(state)
+
+    def clear(self):
+        self.unit = null_unit
+        self.user = null_user
+        self._unit_options = None
+        self.prefs = None
+        self.messages = []
+
+    def _get_rights(self):
+        return self.user.rights | self.unit.rights
+    rights = property(_get_rights)
+
+    def __nonzero__(self):
+        return bool(self.user) and self.prefs is not None
+
+    def admin_log(self, db, event_type):
+        admin_log(db, self.user.user_id, event_type)
+
+    def user_log(self, db, event_type, user_id=None, **kw):
+        if user_id is None and self.user:
+            user_id = self.user.user_id
+        user_log(db, user_id, event_type, **kw)
+
+    def set_unit(self, db, unit_id):
+        for id, name in self.unit_options:
+            if id == unit_id:
+                self.unit = units[unit_id]
+                return
+
+    def _get_user(self, db, username):
+        query = db.query('users')
+        query.where('lower(users.username) = %s', username.lower())
+        return query.fetchone()
+
+    def authenticate_user(self, db, username, password):
+        if not username:
+            raise CredentialError('Please enter a user name')
+        user = self._get_user(db, username)
+        if user is None:
+            raise pwerror
+        remain = timelock_remain(user)
+        if remain:
+            raise TooManyAttempts('Too many login attempts with a bad '
+                                  'password - please wait %d minutes %d '
+                                  'seconds' % (remain.minute, remain.second))
+        try:
+            pwd_check(user, password)
+        except PasswordError:
+            user.bad_timestamp = DateTime.now()
+            user.bad_attempts += 1
+            user.db_update()
+            self.user_log(db, 'LOGIN FAILED - INCORRECT PASSWORD', 
+                            user_id=user.user_id)
+            db.commit()
+            if user.bad_attempts == max_bad_passwords:
+                notify.too_bad_notify(user)
+            raise pwerror
+        if not user.enabled:
+            self.user_log(db, 'LOGIN FAILED - DISABLED', user_id=user.user_id)
+            db.commit()
+            raise DisabledUser('User account not activated', user)
+        self.unit_options = get_user_units(db, user.user_id, 
+                            ('unit_id', 'name'))
+        if not self.unit_options:
+            self.user_log(db, 'LOGIN FAILED - NO UNIT', user_id=user.user_id)
+            db.commit()
+            raise DisabledUser('User account not activated (user is not a '
+                               'member of any valid units)', user)
+        # All okay now - set up user credentials
+        self.user = users.add(user)
+        if user.bad_attempts:
+            user.bad_attempts = 0
+        if pwcrypt.need_upgrade(user.password):
+            user.password = pwcrypt.new_crypt(password)
+            self.user_log(db, 'Password upgrade', user_id=user.user_id)
+        user.db_update()
+        self.user_log(db, 'LOGIN OK', user_id=user.user_id)
+        db.commit()
+        if not user.privacy:
+            self.messages.append(privacy_reminder)
+        if need_check(user):
+            self.messages.append(details_reminder)
+        self.prefs = Preferences(user.user_id, user.preferences)
+        if len(self.unit_options) == 1:
+            self.set_unit(db, self.unit_options[0][0])
+        else:
+            current_unit = self.prefs.get('current_unit')
+            if current_unit is not None:
+                self.set_unit(db, current_unit)
+            if not self.unit:
+                raise SelectUnit
+
+    def commit_prefs(self, db, immediate=False):
+        if self.prefs is not None:
+            self.prefs.commit(db, immediate)
+
+    def auth_override(self, db, username):
+        """
+        Set up credentials as user without requiring authentication. This
+        is used by the command line tools to do things such as exporting
+        with a specified user's rights.
+        """
+        if not username:
+            raise CredentialError('Unknown user name')
+        user = self._get_user(db, username)
+        if user is None:
+            raise CredentialError('Unknown user name')
+        self.prefs = Preferences(user.user_id, user.preferences)
+        self.user = users.add(user)
+        self.unit_options = get_user_units(db, user.user_id, 
+                                            ('unit_id', 'name'))
+        if not self.unit_options:
+            raise DisabledUser('User is not a member of any valid units')
+        elif len(self.unit_options) == 1:
+            self.set_unit(db, self.unit_options[0][0])
+        else:
+            raise SelectUnit
+
+
+class NewPass:
+    def __init__(self):
+        self.old = ''
+        self.new_a = ''
+        self.new_b = ''
+
+    def has_new(self):
+        return self.new_a or self.new_b
+
+    def set(self, user):
+        """
+        Set a new password on the given user (without checking old password)
+        """
+        try:
+            pwcrypt.strong_pwd(self.new_a)
+        except pwcrypt.Error, e:
+            raise PasswordError(str(e))
+        if self.new_a != self.new_b:
+            raise PasswordError("Passwords don't match - try again")
+        user.password = pwcrypt.new_crypt(self.new_a)
+
+
+def reset_attempts(user):
+    db = user.db()
+    curs = db.cursor()
+    try:
+        # We use an explicit update, rather than letting the dbrow do it as we
+        # may not (yet) want to apply other changes that are in the dbrow.
+        dbobj.execute(curs, 'UPDATE users SET bad_attempts = 0'
+                            ' WHERE user_id = %s', (user.user_id,))
+        user.reset_initial('bad_attempts', 0)
+    finally:
+        curs.close()
+
+
+def revoke_invite(db, user_id):
+    query = db.query('users')
+    query.where('user_id=%s AND username IS NULL', user_id)
+    row = query.fetchone()
+    if row is None:
+        return
+    row.db_delete()
+    return row.fullname
+
+
+def mark_checked(user):
+    db = user.db()
+    curs = db.cursor()
+    now = DateTime.now()
+    try:
+        dbobj.execute(curs, 'UPDATE users'
+                            ' SET checked_timestamp = %s'
+                            ' WHERE user_id = %s', (now, user.user_id))
+        user.reset_initial('checked_timestamp', now)
+    finally:
+        curs.close()
+
+
+def need_check(user):
+    if not config.user_check_interval or user.is_new():
+        return False
+    days = DateTime.DateTimeDelta(config.user_check_interval)
+    threshold = DateTime.now() - days
+    return not user.checked_timestamp or user.checked_timestamp < threshold
diff --git a/casemgr/dataexport.py b/casemgr/dataexport.py
new file mode 100644
index 0000000..94ba516
--- /dev/null
+++ b/casemgr/dataexport.py
@@ -0,0 +1,465 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Export row-per-case data.
+
+This is complicated, as a case has multiple associated forms, and
+multiple instances of the forms. Form instances can also be associated
+with different versions of the form definition.
+"""
+
+import time
+import csv
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+from mx import DateTime
+from cocklebur import form_ui, dbobj
+from cocklebur.filename_safe import filename_safe
+from casemgr import globals, caseaccess, syndrome, casetags
+
+ctrlre = re.compile(r'[\000-\037]+')
+
+ISO_fmt = '%Y-%m-%d %H:%M:%S'
+
+def value_format(value, strip_newlines=False):
+    if value is None:
+        return ''
+    elif type(value) is DateTime.DateTimeType:
+        return value.strftime(ISO_fmt)
+    elif isinstance(value, bool):
+        if value:
+            return 't'
+        else:
+            return ''
+    value = str(value)
+    if strip_newlines:
+        value = ctrlre.sub(' ', value)
+    return value
+
+
+class Forms:
+
+    """
+    Fetch a list of forms applicable to a given syndrome.
+    """
+
+    def __init__(self, db, names):
+        self.forms = []
+        if names:
+            query = db.query('forms', order_by='forms.name')
+            query.where_in('forms.label', names)
+            self.forms = query.fetchall()
+
+    def __getitem__(self, i):
+        return self.forms[i]
+
+    def __iter__(self):
+        return iter(self.forms)
+
+    def __len__(self):
+        return len(self.forms)
+
+
+class FormFormatterBase:
+
+    def __init__(self, form_name, strip_newlines):
+        self.form_name = form_name
+        self.strip_newlines = strip_newlines
+        self.columns = ['form_id', 'form_date']
+        self.columns_seen = set(self.ignore)
+        self.clear()
+        self.table_map = {}
+        self.form_count_by_case = {}
+        self.form_versions = set()
+        self.form_count = None
+
+    def seen_form(self, id, form_version):
+        self.form_count_by_case[id] = self.form_count_by_case.get(id, 0) + 1
+        self.form_versions.add(form_version)
+
+    def get_form_count(self):
+        return max(self.form_count_by_case.values())
+
+    def add_form_columns(self, version):
+        form = globals.formlib.load(self.form_name, version)
+        self.table_map[version] = form.table
+        for col in form.columns:
+            if col.name not in self.columns_seen:
+                self.columns.append(col.name)
+                self.columns_seen.add(col.name)
+
+    def clear(self):
+        self.form_by_summ_id = {}
+
+    def preload(self, db, version, summary_ids):
+        query = db.query(self.table_map[version])
+        query.where_in('summary_id', summary_ids)
+        for row in query.fetchdict():
+            summary_id = row['summary_id']
+            row['form_id'] = form_ui.form_id(summary_id)
+            self.form_by_summ_id[summary_id] = row
+
+    def row_format(self, row):
+        if row is None:
+            return [''] * len(self.columns)
+        return [value_format(row.get(col), self.strip_newlines)
+                for col in self.columns]
+
+
+form_row_formatters = {}
+
+class FormFormatterClassic(FormFormatterBase):
+
+    ignore = 'case_id', 'summary_id', 'form_date'
+
+    def col_labels(self):
+        self.form_count = self.get_form_count()
+        for form_version in self.form_versions:
+            self.add_form_columns(form_version)
+        if self.form_count == 1:
+            return ['%s.%s' % (self.form_name, col) for col in self.columns]
+        else:
+            return ['%s.%s.%d' % (self.form_name, col, inst) 
+                    for inst in range(self.form_count)
+                    for col in self.columns]
+
+    def col_values(self, summ_ids):
+        values = []
+        for i in range(self.form_count):
+            row = None
+            if i < len(summ_ids):
+                row = self.form_by_summ_id.get(summ_ids[i][1])
+            values.extend(self.row_format(row))
+        return values
+
+form_row_formatters['classic'] = FormFormatterClassic
+
+
+class FormFormatterDoHA(FormFormatterBase):
+
+    ignore = 'case_id', 'form_date'
+
+    def col_labels(self):
+        self.form_count = self.get_form_count()
+        for form_version in self.form_versions:
+            self.add_form_columns(form_version)
+        labels = []
+        for i in range(self.form_count):
+            labels.extend(['form_label', 'form_version'])
+            if self.form_count == 1:
+                labels.extend(self.columns)
+            else:
+                for col in self.columns:
+                    labels.append('%s%d' % (col, i))
+        return labels
+
+    def col_values(self, summ_ids):
+        values = []
+        for i in range(self.form_count):
+            values.append(self.form_name)
+            row = None
+            if i < len(summ_ids):
+                row = self.form_by_summ_id.get(summ_ids[i][1])
+            if row is None:
+                values.append('')
+            else:
+                values.append(summ_ids[i][0])
+            values.extend(self.row_format(row))
+        return values
+
+form_row_formatters['doha'] = FormFormatterDoHA
+
+
+class FormFormatterForm(FormFormatterBase):
+    ignore = ()
+
+    def col_labels(self):
+        self.form_count = self.get_form_count()
+        for form_version in self.form_versions:
+            self.add_form_columns(form_version)
+        return ['%s.%s' % (self.form_name, col) for col in self.columns]
+
+    def col_values(self, summ_ids):
+        for form_version, summ_id in summ_ids:
+            yield self.row_format(self.form_by_summ_id.get(summ_id))
+
+form_row_formatters['form'] = FormFormatterForm
+
+
+class RowFormatterBase(object):
+
+    def __init__(self, format, strip_newlines=False):
+        self.forms_by_name = {}
+        self.include_forms = None
+        self.case_cols = None
+        self.fetch_cols = ('cases.*', 'persons.*')
+        self.form_formatter = form_row_formatters[format]
+        self.strip_newlines = strip_newlines
+
+    def seen_form(self, id, form_name, form_version):
+        try:
+            form = self.forms_by_name[form_name]
+        except KeyError:
+            form = self.form_formatter(form_name, self.strip_newlines)
+            self.forms_by_name[form_name] = form
+        form.seen_form(id, form_version)
+
+    def _get_case_cols(self, db):
+        if self.case_cols is None:
+            query = db.query('cases', limit=0)
+            query.join('JOIN persons USING (person_id)')
+            curs = db.cursor()
+            try:
+                query.execute(curs, self.fetch_cols)
+                self.case_cols = dbobj.cursor_cols(curs)
+            finally:
+                curs.close()
+            # Remove duplicate person_id (cases refs persons).
+            self.case_cols.remove('person_id')
+            # Remove duplicate update_time (on cases, and on persons)
+            self.case_cols.remove('last_update')
+            self.case_cols.remove('last_update')
+
+    def set_include_forms(self, include_forms):
+        self.include_forms = include_forms
+
+    def preload(self, db, cases):
+        id_idx = self.case_cols.index('case_id')
+        query = db.query('cases')
+        query.join('JOIN persons USING (person_id)')
+        query.where_in('case_id', [case.id for case in cases])
+        self.cases = {}
+        for row in query.fetchcols(self.case_cols):
+            self.cases[row[id_idx]] = row
+        self.cases_tags = casetags.CasesTags(self.cases.keys())
+        for name in self.include_forms:
+            self.forms_by_name[name].clear()
+        formvers_summids = ExportCase.summid_by_form_version(cases, 
+                                                             self.include_forms)
+        for (form_name, form_version), summ_ids in formvers_summids:
+            form = self.forms_by_name[form_name]
+            form.preload(db, form_version, summ_ids)
+
+    def col_labels(self, db):
+        self._get_case_cols(db)
+        labels = list(self.case_cols)
+        labels.append('tags')
+        for name in self.include_forms:
+            labels.extend(self.forms_by_name[name].col_labels())
+        return labels
+
+
+row_formatters = {}
+
+class RowPerCaseFormatter(RowFormatterBase):
+
+    def rows(self, cases):
+        for case in cases:
+            values = [value_format(value, self.strip_newlines)
+                      for value in self.cases[case.id]]
+            values.append(str(self.cases_tags.get(case.id, '')))
+            summid_by_form = case.summid_by_form()
+            for name in self.include_forms:
+                form_fmt = self.forms_by_name[name]
+                summids = summid_by_form.get(form_fmt.form_name, [])
+                values.extend(form_fmt.col_values(summids))
+            yield values
+
+
+
+row_formatters['classic'] = RowPerCaseFormatter
+row_formatters['doha'] = RowPerCaseFormatter
+
+
+class RowPerFormFormatter(RowFormatterBase):
+
+    def set_include_forms(self, include_forms):
+        if len(include_forms) != 1:
+            raise globals.Error('Must select one (and only one) form')
+        self.include_forms = include_forms
+
+    def rows(self, cases):
+        assert len(self.include_forms) == 1
+        form_fmt = self.forms_by_name[self.include_forms[0]]
+        for case in cases:
+            case_cols = [value_format(value, self.strip_newlines)
+                         for value in self.cases[case.id]]
+            case_cols.append(str(self.cases_tags.get(case.id, '')))
+            summid_by_form = case.summid_by_form()
+            summids = summid_by_form.get(form_fmt.form_name, [])
+            for form_cols in form_fmt.col_values(summids):
+                yield case_cols + form_cols
+
+row_formatters['form'] = RowPerFormFormatter
+
+
+class ExportCase:
+    """
+    Represents a case to be exported, recording information about form
+    instances associated with the case.
+    """
+    def __init__(self, id):
+        self.id = id
+        self.forms = []
+
+    def add_summ_id(self, summary_id, form_name, form_version):
+        self.forms.append((summary_id, form_name, form_version))
+
+    def summid_by_form(self):
+        summids = {}
+        for summary_id, form_name, form_version in self.forms:
+            form_summids = summids.setdefault(form_name, [])
+            form_summids.append((form_version, summary_id))
+        return summids
+
+    def summid_by_form_version(cases, include_forms):
+        summid_by = {}
+        for case in cases:
+            for summary_id, form_name, form_version in case.forms:
+                if form_name in include_forms:
+                    key = form_name, form_version
+                    summid_by.setdefault(key, []).append(summary_id)
+        return summid_by.items()
+    summid_by_form_version = staticmethod(summid_by_form_version)
+
+
+class ExportCases:
+    """
+    This class records info about relevent cases, and returns them in
+    bite-sized chunks.
+    """
+    def __init__(self):
+        self.cases_in_order = []
+        self.cases_by_id = {}
+
+    def add_summ_id(self, id, summary_id, form_name, form_version):
+        try:
+            export_case = self.cases_by_id[id]
+        except KeyError:
+            export_case = ExportCase(id)
+            self.cases_by_id[id] = export_case
+            self.cases_in_order.append(export_case)
+        if summary_id is not None:
+            export_case.add_summ_id(summary_id, form_name, form_version)
+
+    def yield_chunks(self, chunksize=1000):
+        i = 0
+        while i < len(self.cases_in_order):
+            yield self.cases_in_order[i:i+chunksize]
+            i += chunksize
+
+    def __len__(self):
+        return len(self.cases_in_order)
+
+
+class CaseExporter:
+
+    def __init__(self, credentials, syndrome_id,
+                 format='classic',
+                 deleted='n',
+                 strip_newlines=False):
+        self.credentials = credentials
+        self.syndrome_id = syndrome_id
+        self.format = format
+        self.strip_newlines = strip_newlines
+        # Collect names of all forms *in use* for this syndrome
+        # as well as summary_ids, etc
+        query = globals.db.query('cases', distinct=True)
+        query.join('LEFT JOIN case_form_summary USING (case_id)')
+        query.where('syndrome_id = %s', syndrome_id)
+        if deleted != 'n':
+            query.where('NOT case_form_summary.deleted')
+        caseaccess.acl_query(query, self.credentials, deleted=deleted)
+        forms_used = set()
+        self.export_cases = ExportCases()
+        row_formatter_cls = row_formatters[self.format]
+        self.row_formatter = row_formatter_cls(self.format, self.strip_newlines)
+        rows = query.fetchcols(('cases.case_id', 
+                                'summary_id', 'form_label', 'form_version'))
+        for case_id, summary_id, form_name, form_version in rows:
+            self.export_cases.add_summ_id(case_id, summary_id,
+                                          form_name, form_version)
+            if summary_id is not None:
+                self.row_formatter.seen_form(case_id, form_name, form_version)
+                forms_used.add(form_name)
+        self.forms = Forms(globals.db, forms_used)
+
+    def entity_info(self):
+        count = len(self.export_cases)
+        if count == 0:
+            return 'are no records'
+        elif count == 1:
+            return 'is 1 record'
+        else:
+            return 'are %s records' % count
+
+    def filename(self):
+        syndrome_name = filename_safe(syndrome.syndromes[self.syndrome_id].name)
+        return 'nec-%s-%s.csv' % (syndrome_name,
+                                 time.strftime('%Y%m%d-%H%M'))
+
+    def row_gen(self, include_forms):
+        self.row_formatter.set_include_forms(include_forms)
+        yield self.row_formatter.col_labels(globals.db)
+        for cases in self.export_cases.yield_chunks():
+            self.row_formatter.preload(globals.db, cases)
+            for row in self.row_formatter.rows(cases):
+                yield row
+
+    def csv_write(self, include_forms, f):
+        csv.writer(f).writerows(self.row_gen(include_forms))
+
+
+class ContactExporter:
+
+    forms = None
+
+    def __init__(self, credentials, syndrome_id,
+                 format='classic',
+                 deleted='n',
+                 strip_newlines=False):
+        self.syndrome_id = syndrome_id
+        self.credentials = credentials
+        self.format = format
+        self.strip_newlines = strip_newlines
+        self.deleted = deleted
+
+    def filename(self):
+        syndrome_name = filename_safe(syndrome.syndromes[self.syndrome_id].name)
+        return 'nec-contacts-%s-%s.csv' % (syndrome_name,
+                                 time.strftime('%Y%m%d-%H%M'))
+
+    def row_gen(self, include_forms):
+        yield 'id_a', 'id_b', 'contact_type', 'contact_date'
+        query = globals.db.query('case_contacts')
+        query.join('JOIN cases USING (case_id)')
+        query.join('LEFT JOIN contact_types USING (contact_type_id)')
+        query.where('case_id < contact_id')
+        caseaccess.acl_query(query, self.credentials, deleted=self.deleted)
+        cols = 'case_id', 'contact_id', 'contact_type', 'contact_date'
+        for row in query.fetchcols(cols):
+            if row[-1]:
+                row = row[:-1] + (row[-1].strftime(ISO_fmt),)
+            yield row
+
+    def csv_write(self, include_forms, f):
+        csv.writer(f).writerows(self.row_gen(include_forms))
diff --git a/casemgr/dataimp/__init__.py b/casemgr/dataimp/__init__.py
new file mode 100644
index 0000000..59ddc87
--- /dev/null
+++ b/casemgr/dataimp/__init__.py
@@ -0,0 +1,20 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+from casemgr.dataimp.common import *
diff --git a/casemgr/dataimp/common.py b/casemgr/dataimp/common.py
new file mode 100644
index 0000000..79fa485
--- /dev/null
+++ b/casemgr/dataimp/common.py
@@ -0,0 +1,23 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals
+
+class Error(globals.Error): pass
+class DataImpEditorError(Error): pass
+class DataImpElementError(Error): pass
diff --git a/casemgr/dataimp/dataimp.py b/casemgr/dataimp/dataimp.py
new file mode 100644
index 0000000..67b2d23
--- /dev/null
+++ b/casemgr/dataimp/dataimp.py
@@ -0,0 +1,461 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj, form_ui, agelib
+from casemgr import globals, demogfields, cases, caseaccess, persondupe, \
+                    syndrome, form_summary
+from casemgr.dataimp import elements, datasrc
+from casemgr.dataimp.datasrc import DataImpError
+
+Error = DataImpError
+
+catch_errors = (
+    globals.Error,
+    dbobj.DatabaseError, 
+    form_ui.FormError,
+)
+
+class TooManyErrors(DataImpError): pass
+
+# NOTE: Person merging must not merge persons from sources other than the local
+# (null) source, or it should clear the source when merging.
+
+class ImportErrors:
+
+    MAX_ERRORS = 100
+
+    def __init__(self):
+        self.nerrors = 0
+        self.errors = {None: []}
+
+    def __nonzero__(self):
+        return bool(self.nerrors)
+
+    def __iter__(self):
+        recnums = self.errors.keys()
+        recnums.sort()
+        for recnum in recnums:
+            for msg in self.errors[recnum]:
+                yield str(msg)
+
+    def count(self):
+        return self.nerrors
+
+    def error(self, msg, recnum=None, row=None):
+        self.nerrors += 1
+        if row:
+            recnum = row.record_num
+            msg = 'record %s (line %s): %s' % (recnum, row.line_num, msg)
+        elif recnum is not None:
+            msg = 'record %s: %s' % (recnum, msg)
+        self.errors.setdefault(recnum, []).append(msg)
+        if self.nerrors == self.MAX_ERRORS:
+            self.errors[None].insert(0, 'More than %d errors, giving up' % 
+                                        self.MAX_ERRORS)
+            raise TooManyErrors
+
+    def __contains__(self, recnum=None):
+        return recnum in self.errors
+
+    def get(self, recnum=None):
+        return self.errors.get(recnum, [])
+
+    def __repr__(self):
+        return '\n'.join(self)
+
+
+class ColProc(object):
+
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+
+    def set(self, ns, value):
+        if (value is not None and self.options is not None 
+                and value not in self.options):
+            raise Error('%r not a valid choice' % value)
+        setattr(ns, self.target, value)
+
+
+class ImportCol(ColProc):
+
+    def get(self, row):
+        value = self.rule.translate(row.fields[self.index].strip())
+        if value == '':
+            value = None
+        return value
+
+
+class ImportAgeCol(ImportCol):
+
+    def get(self, row):
+        value = row.fields[self.index].strip()
+        if not value and self.age_index is not None:
+            value = row.fields[self.age_index].strip()
+        if value:
+            try:
+                agelib.Age.parse(value)
+            except agelib.Error:
+                value = self.rule.translate(value)
+        else:
+            value = None
+        return value
+
+
+class ImportMultiCol(ImportCol):
+
+    def __init__(self, **kwargs):
+        ImportCol.__init__(self, **kwargs)
+        assert self.options 
+        self.target = self.target.lower()
+        self.options = set([o.lower() for o in self.options])
+
+    def set(self, ns, value):
+        value = set([o.lower() for o in value])
+        invalid = (value - self.options)
+        if invalid:
+            raise Error('%s not valid choice(s)' % (', '.join(invalid)))
+        for name in self.options:
+            setattr(ns, self.target+name, name in value)
+
+
+class ValueCol(ColProc):
+
+    def get(self, row_ignore):
+        return self.rule.value
+
+
+class NS:
+    def __init__(self, **kw):
+        self.__dict__.update(kw)
+
+
+class GroupBase(object):
+    
+    pass
+
+
+class DemogGroup(GroupBase):
+    def __init__(self, importer):
+        self.label = 'Demographics'
+        fields = demogfields.get_demog_fields(globals.db, importer.syndrome_id)
+        fields = fields.context_fields('case')
+        self.col_procs = []
+        importer.key_proc = None
+        for field in fields:
+            try:
+                rule = importer.importrules[field.name]
+            except KeyError:
+                continue
+            try:
+                method = None
+                # rather use str.rsplit, but it's not in py 2.3
+                # ns, field = col_proc.field.rsplit('.', 1)
+                names = field.field.split('.')
+                args = {
+                    'name': field.name, 
+                    'label': field.label, 
+                    'entity': names[1],
+                    'target': names[2],
+                    'rule': rule,
+                    'outtrans': field.outtrans,
+                    'options': None,
+                }
+                if field.name == 'DOB':
+                    method = ImportAgeCol
+                    args['index'] = importer.dataimp_rows.col_idx(rule.src)
+                    age_idx = None
+                    if isinstance(rule, elements.ImportAgeSource):
+                        age_idx = importer.dataimp_rows.col_idx(rule.age)
+                    args['age_index'] = age_idx
+                elif isinstance(rule, elements.ImportSource):
+                    method = ImportCol
+                    args['index'] = importer.dataimp_rows.col_idx(rule.src)
+                elif isinstance(rule, elements.ImportFixed):
+                    method = ValueCol
+                elif isinstance(rule, elements.ImportIgnore):
+                    continue
+                else:
+                    raise Error('unknown rule type: %r' % rule)
+                if field.optionexpr is not None:
+                    args['options'] = set([v[0] for v in field.optionexpr()])
+                col_proc = method(**args)
+                self.col_procs.append(col_proc)
+                if field.name == 'local_case_id':
+                    importer.key_proc = col_proc
+            except Error, e:
+                importer.error('field %r: %s' % (field.label, e))
+
+    def apply(self, importer, case, row):
+        for col_proc in self.col_procs:
+            try:
+                value = col_proc.get(row)
+                if col_proc.entity == 'person':
+                    col_proc.set(case.person, value)
+                elif col_proc.entity == 'case_row':
+                    col_proc.set(case.case_row, value)
+            except catch_errors, e:
+                importer.error('%s: %s' % (col_proc.label, e), row=row)
+
+    def validate(self, importer, case, row):
+        try:
+            case.validate()
+            return True
+        except catch_errors, e:
+            importer.error(e, row=row)
+            return False
+
+    def update(self, importer, case, row):
+        self.apply(importer, case, row)
+        if self.validate(importer, case, row):
+            case.update()
+
+    def __len__(self):
+        return len(self.col_procs)
+
+    def headings(self):
+        return [col_proc.label for col_proc in self.col_procs]
+
+    def preview(self, importer, case, row):
+        ns = NS(case=case)
+        self.apply(importer, case, row)
+        self.validate(importer, case, row)
+        preview = []
+        for col_proc in self.col_procs:
+            try:
+                preview.append(col_proc.outtrans(ns))
+            except catch_errors, e:
+                preview.append('???')
+        return preview
+
+
+class FormGroup(GroupBase):
+    def __init__(self, importer, form_rules):
+        self.name = form_rules.name
+        self.formdataimp = form_summary.FormDataImp(importer.syndrome_id,
+                                                    self.name, 
+                                                  importer.importrules.srclabel)
+        self.label = self.formdataimp.form.name
+        self.version = self.formdataimp.form.cur_version
+        form = globals.formlib.load(self.name, self.version)
+        self.col_procs = []
+        for input in form.get_inputs():
+            try:
+                rule = form_rules[input.column]
+            except KeyError:
+                continue
+            label = input.label or input.column
+            try:
+                method = None
+                args = {
+                    'name': input.column, 
+                    'label': label, 
+                    'target': input.get_column_name(),
+                    'rule': rule,
+                    'outtrans': input.outtrans,
+                    'options': None,
+                }
+                if isinstance(rule, elements.ImportMultiValue):
+                    method = ImportMultiCol
+                    args['index'] = importer.dataimp_rows.col_idx(rule.src)
+                elif isinstance(rule, elements.ImportSource):
+                    method = ImportCol
+                    args['index'] = importer.dataimp_rows.col_idx(rule.src)
+                elif isinstance(rule, elements.ImportFixed):
+                    method = ValueCol
+                elif isinstance(rule, elements.ImportIgnore):
+                    continue
+                else:
+                    raise Error('unknown rule type: %r' % rule)
+                if hasattr(input, 'choices'):
+                    args['options'] = set([v[0] for v in input.choices])
+                self.col_procs.append(method(**args))
+            except Error, e:
+                importer.error('form %r input %r: %s' % (self.label, label, e))
+
+    def apply(self, importer, ns, row):
+        for col_proc in self.col_procs:
+            try:
+                col_proc.set(ns, col_proc.get(row))
+            except catch_errors, e:
+                importer.error('%s: %s' % (col_proc.label, e), row=row)
+
+    def validate(self, importer, ns, row):
+        form = globals.formlib.load(self.name, self.version)
+        form_errors = form.validate(ns)
+        if form_errors:
+            for error in form_errors.in_order:
+                importer.error('%s: %s' % (self.label, error), row=row)
+            return False
+        return True
+
+    def update(self, importer, case, row):
+        try:
+            edit_form, form_data = self.formdataimp.edit(case.case_row.case_id)
+            self.apply(importer, form_data, row)
+        except Error, e:
+            edit_form.abort()
+            importer.error('%s: %s' % (self.label, e), row=row)
+        else:
+            if self.validate(importer, form_data, row):
+                edit_form.update()
+            else:
+                edit_form.abort()
+
+    def __len__(self):
+        return len(self.col_procs)
+
+    def headings(self):
+        return [col_proc.label for col_proc in self.col_procs]
+
+    def preview(self, importer, case, row):
+        ns = NS()
+        self.apply(importer, ns, row)
+        self.validate(importer, ns, row)
+        return [col_proc.outtrans(ns) for col_proc in self.col_procs]
+
+
+class ImportBase(object):
+
+    def __init__(self, syndrome_id, dataimp_src, importrules):
+        self.errors = ImportErrors()
+        self.col_procs = None
+        self.syndrome_id = syndrome_id
+        self.importrules = importrules
+        self.locked_cases = []
+        self.rownum = None
+        if not dataimp_src:
+            return self.error('No data source selected')
+        if not importrules.srclabel:
+            return self.error('You must supply a data source name (in Params)')
+        self.dataimp_rows = dataimp_src.row_iterator(self.importrules)
+        self.demog_group = DemogGroup(self)
+        self.groups = [self.demog_group]
+        for form_rules in self.importrules.forms():
+            self.groups.append(FormGroup(self, form_rules))
+
+    def yield_keys(self):
+        assert self.key_proc is not None
+        for row in self.dataimp_rows:
+            yield self.key_proc.get(row)
+
+    def error(self, msg, **kw):
+        self.errors.error(msg, **kw)
+
+
+class PreviewImport(ImportBase):
+
+    def __init__(self, credentials, syndrome_id, dataimp_src, importrules):
+        ImportBase.__init__(self, syndrome_id, dataimp_src, importrules)
+        self.rows = []
+        self.header = []
+        self.group_header = []
+        if self.errors:
+            return
+        for group in self.groups:
+            self.group_header.append((group.label, len(group)))
+            self.header.extend(group.headings())
+        self.n_cols = len(self.header)
+        try:
+            for row in self.dataimp_rows:
+                case = cases.new_case(credentials, self.syndrome_id,
+                                      defer_case_id=True)
+                row_pp = []
+                for group in self.groups:
+                    row_pp.extend(group.preview(self, case, row))
+                self.rows.append(row_pp)
+        except TooManyErrors:
+            pass
+        except Error, e:
+            # For aborting errors
+            self.error(e)
+
+
+def locked_case_ids(credentials, syndrome_id, dataimp_src, importrules):
+    importer = ImportBase(syndrome_id, dataimp_src, importrules)
+    if importer.key_proc is None:
+        raise Error('No key/ID column defined')
+    local_ids = importer.yield_keys()
+    query = globals.db.query('cases')
+    query.join('JOIN persons USING (person_id)')
+    caseaccess.acl_query(query, credentials)
+    query.where('(data_src is null OR data_src != %s)', importrules.srclabel)
+    query.where('(data_src is null OR data_src != %s)', importrules.srclabel)
+    query.where_in('local_case_id', local_ids)
+    return query.fetchcols('case_id')
+
+
+class DataImp(ImportBase):
+
+    def __init__(self, credentials, syndrome_id, dataimp_src, importrules):
+        ImportBase.__init__(self, syndrome_id, dataimp_src, importrules)
+        if self.errors:
+            return
+        self.update_cnt = self.new_cnt = self.conflict_cnt = 0
+        try:
+            for row in self.dataimp_rows:
+                case = None
+                dup_person_id = None
+                if self.key_proc is not None:
+                    key = self.key_proc.get(row)
+                    # XXX We may get a dbapi.IntegrityError exception here,
+                    # because the local_case_id column does not have a unique
+                    # constraint, and this query may return multiple rows - how
+                    # to fix?
+                    try:
+                        case = cases.case_query(credentials, 
+                                                syndrome_id=self.syndrome_id,
+                                                local_case_id=key)
+                    except dbobj.IntegrityError:
+
+                        self.error('%r %s is not unique' % (self.key_proc.label, key))
+                        continue
+                if (case is not None 
+                        and case.person.data_src != self.importrules.srclabel):
+                    if self.importrules.conflicts == 'duplicate':
+                        dup_person_id = case.person.person_id
+                        case = None
+                    else:
+                        self.locked_cases.append(key)
+                        continue
+                if case is None:
+                    case = cases.new_case(credentials, self.syndrome_id,
+                                          defer_case_id=False)
+                    case.person.data_src = self.importrules.srclabel
+                    self.new_cnt += 1
+                else:
+                    self.update_cnt += 1
+                for group in self.groups:
+                    group.update(self, case, row)
+                if dup_person_id is not None:
+                    persondupe.conflict(dup_person_id, case.person.person_id)
+                    self.conflict_cnt += 1
+            self.status = ('Loaded %d records, updated %d, created %d' %
+                            (self.update_cnt + self.new_cnt,
+                             self.update_cnt, self.new_cnt)) 
+            if self.importrules.conflicts == 'duplicate' and self.conflict_cnt:
+                self.status += ' (%s duplicates created)' % self.conflict_cnt
+        except TooManyErrors:
+            pass
+        except Error, e:
+            self.error(e)
diff --git a/casemgr/dataimp/datasrc.py b/casemgr/dataimp/datasrc.py
new file mode 100644
index 0000000..7ce9cdb
--- /dev/null
+++ b/casemgr/dataimp/datasrc.py
@@ -0,0 +1,309 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import os
+import csv
+import codecs
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import utils, datetime
+
+from casemgr import globals
+
+import config   # XXX scratchdir
+
+if sys.version_info < (2,4):
+    # Monkey patch utf16 codecs if python version is less than 2.4 to make up
+    # for the lack of a .readline() method.  See
+    # http://bugs.python.org/issue920680
+
+    def readline(self):
+        if not self._utf16_readline_buffer:
+            self._utf16_readline_buffer = [u'']
+        while True:
+            line = self._utf16_readline_buffer.pop(0)
+            if len(self._utf16_readline_buffer) > 0:
+                return line
+            chunk = self.read(256)
+            if not chunk:
+                return line
+            line += chunk
+            self._utf16_readline_buffer = line.splitlines(True)
+
+    from encodings import utf_16
+    from encodings import utf_16_be
+    from encodings import utf_16_le
+
+    for mod in utf_16, utf_16_be, utf_16_le:
+        mod.StreamReader.readline = readline
+        mod.StreamReader._utf16_readline_buffer = None
+
+
+class DataImpError(globals.Error): pass
+
+
+class Decoder:
+    """
+    Read lines from /filename/, decode using /encoding/ and return each line
+    re-encoded as utf-8 (as the csv module does not support unicode at this
+    time). Record line number for error reporting.
+    """
+    name = None
+
+    def __init__(self, filename, encoding):
+        self.filename = filename
+        self.line_num = 0
+        self.f = codecs.open(filename, 'rb', encoding)
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        self.line_num += 1
+        try:
+            return self.f.next().encode('utf-8')
+        except UnicodeError, e:
+            raise DataImpError('line %s: %s' % (self.line_num, e))
+
+    def close(self):
+        self.f.close()
+
+
+class Row(object):
+
+    __slots__ = ('fields', 'line_num', 'record_num')
+
+    def __init__(self, fields, line_num, record_num):
+        self.fields = fields
+        self.line_num = line_num
+        self.record_num = record_num
+
+
+class SrcLoader:
+
+    def __init__(self, name, filename, encoding, fieldsep, mode):
+        self.name = name
+        self.filename = filename
+        self.encoding = encoding
+        self.fieldsep = fieldsep
+        self.mode = mode
+        self.n_cols = None
+        self.n_rows = None
+        self.col_names = None
+        self.__utf8_lines = Decoder(self.filename, self.encoding)
+        self.__csv_reader = iter(csv.reader(self.__utf8_lines, 
+                                            delimiter=self.fieldsep))
+        if self.mode == 'named':
+            try:
+                col_names = self.__csv_reader.next()
+            except StopIteration:
+                return
+            self.col_names = [n.strip().lower() for n in col_names]
+            self.col_map = dict([(n, i) for i, n in enumerate(self.col_names)])
+            self.n_cols = len(self.col_names)
+        elif self.mode == 'positional':
+            self.n_cols = None
+        else:
+            raise DataImpError('Unknown import rule mode %r' % mode)
+
+    def __getstate__(self):
+        raise TypeError('Cannot pickle %s' % self.__class__)
+
+    def col_idx(self, name):
+        index = None
+        if self.mode == 'named':
+            try:
+                index = self.col_map[name.strip().lower()]
+            except KeyError:
+                raise DataImpError('Column %r not found in this source' % name)
+        elif self.mode == 'positional':
+            try:
+                index = int(name.strip()) - 1
+            except (TypeError, ValueError):
+                raise DataImpError('Invalid column index %r' % name)
+        return index
+
+    def __iter__(self):
+        self.n_rows = 0
+        start_line = 1
+        try:
+            while 1:
+                start_line = self.__utf8_lines.line_num + 1
+                try:
+                    fields = self.__csv_reader.next()
+                except StopIteration:
+                    break
+                if not fields:
+                    # Last line of file is often empty
+                    continue
+                if self.n_cols is None:
+                    self.n_cols = len(fields)
+                    self.col_names = map(str, range(1, self.n_cols+1))
+                elif len(fields) != self.n_cols:
+                    raise DataImpError('Column count is not constant: '
+                                        'has %s columns, expected %s' % 
+                                        (len(fields), self.n_cols))
+                self.n_rows += 1
+                yield Row(fields, start_line, self.n_rows)
+        except Exception, e:
+            try:
+                self.close()
+            except Exception:
+                pass
+            raise DataImpError('%s: record %s (line %s): %s' %
+                (self.name, self.n_rows, start_line, e))
+        self.close()
+
+    def close(self):
+        if self.__utf8_lines is not None:
+            self.__utf8_lines.close()
+            self.__utf8_lines = None
+            self.__csv_reader = None
+
+
+class Preview:
+
+    def __init__(self):
+        self.error = None
+        self.rows = []
+        self.n_rows = 0
+        self.n_cols = 0
+        self.col_names = []
+        self.colvalues_by_col = {}
+
+    def __nonzero__(self):
+        return bool(self.n_rows)
+
+    def colvalues(self, colname):
+        colvalues = self.colvalues_by_col.get(colname.lower())
+        if colvalues:
+            colvalues = list(colvalues)
+            colvalues.sort()
+        return colvalues
+
+    def colpreview(self, colname):
+        if self.col_names:
+            try:
+                colnum = self.col_names.index(colname.lower())
+            except ValueError:
+                pass
+            else:
+                return [row[colnum] for row in self.rows]
+        return []
+
+
+class SrcPreview(Preview):
+
+    def __init__(self, datasrc_rows):
+        Preview.__init__(self)
+        try:
+            for row in datasrc_rows:
+                if len(self.rows) < 16:
+                    self.rows.append(row.fields)
+                for i, colname in enumerate(datasrc_rows.col_names):
+                    try:
+                        colvalues = self.colvalues_by_col[colname]
+                    except KeyError:
+                        colvalues = self.colvalues_by_col[colname] = set()
+                    if colvalues is not None:
+                        if len(colvalues) > 200:
+                            self.colvalues_by_col[colname] = None
+                        else:
+                            value = row.fields[i].strip()
+                            if value and len(value) < 100:
+                                colvalues.add(value)
+            self.n_rows = datasrc_rows.n_rows
+            self.n_cols = datasrc_rows.n_cols
+            self.col_names = datasrc_rows.col_names
+        except Exception, e:
+            self.error = str(e)
+
+
+class DataImpSrc(object):
+    # XXX scratchdir isn't really the right place for this file - other
+    # users could potentially read it by guessing file names. We will
+    # probably need to write it to the database, keyed by user, but that
+    # may pose problems when the data file is large, as libpq reads the
+    # entire DB response into memory.
+
+    def __init__(self, name, f):
+        self.clear()
+        if not name:
+            return
+        tmpfile = os.path.join(config.scratchdir, 
+                               utils.randfn('imp', 'data'))
+        data_f = open(tmpfile, 'wb')
+        try:
+            while 1:
+                data = f.read(8192)
+                if not data:
+                    break
+                data_f.write(data)
+                self.size += len(data)
+        finally:
+            data_f.close()
+        if not self.size:
+            os.unlink(tmpfile)
+            return
+        self.name = name
+        self.tmpfile = tmpfile
+        self.received = datetime.now()
+
+    def clear(self):
+        self.tmpfile = None
+        self.name = None
+        self.size = 0
+        self.received = None
+        self.preview = Preview()
+
+    def release(self):
+        if self.tmpfile:
+            try:
+                os.unlink(self.tmpfile)
+                self.clear()
+            except OSError:
+                pass
+
+    def __nonzero__(self):
+        if self.tmpfile:
+            if os.path.exists(self.tmpfile):
+                return True
+            self.clear()
+        return False
+
+    def row_iterator(self, importrules):
+        return SrcLoader(self.name, self.tmpfile,
+                         importrules.encoding, 
+                         importrules.fieldsep, 
+                         importrules.mode)
+
+    def update_preview(self, importrules):
+        try:
+            row_iter = self.row_iterator(importrules)
+        except DataImpError, e:
+            self.preview = Preview()
+            self.preview.error = str(e)
+        else:
+            self.preview = SrcPreview(row_iter)
+
+
+NullDataImpSrc = DataImpSrc(None, None)
diff --git a/casemgr/dataimp/editor.py b/casemgr/dataimp/editor.py
new file mode 100644
index 0000000..12a97f8
--- /dev/null
+++ b/casemgr/dataimp/editor.py
@@ -0,0 +1,651 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import sys
+import copy
+try:
+    set
+except NameError:
+    from sets import Set as set
+from cStringIO import StringIO
+
+from cocklebur import utils
+from casemgr import globals, syndrome, demogfields
+from casemgr.dataimp import elements, xmlload, xmlsave
+from casemgr.dataimp.common import DataImpEditorError as Error
+
+CHOOSE = 'Choose...'
+
+# The Action subclasses correspond to the Import rules in "elements",
+# and govern the field editor behaviour
+class Action(object):
+    def __init__(self, importrule):
+        self.initial = isinstance(importrule, self.ctor)
+        self.selected = False
+
+
+class ActionSource(Action):
+    action_name = 'source'
+    desc = 'Import column'
+    ctor = elements.ImportSource
+    src = ''
+
+    def __init__(self, importrule):
+        Action.__init__(self, importrule)
+        if self.initial:
+            self.src = importrule.src
+
+    def to_element(self, name):
+        if self.src and self.src != CHOOSE:
+            return self.ctor(name, self.src)
+        else:
+            return elements.ImportIgnore(name)
+
+
+class ActionAgeSource(ActionSource):
+    action_name = 'agesource'
+    desc = 'DOB and age'
+    ctor = elements.ImportAgeSource
+    age = ''
+
+    def __init__(self, importrule):
+        ActionSource.__init__(self, importrule)
+        if self.initial:
+            self.age = importrule.age
+
+    def to_element(self, name):
+        if self.age == CHOOSE:
+            self.age = ''
+        if self.src and self.src != CHOOSE:
+            return self.ctor(name, self.src, self.age)
+        else:
+            return elements.ImportIgnore(name)
+
+
+class ActionMultiValue(ActionSource):
+    action_name = 'multivalue'
+    desc = 'Multivalue'
+    ctor = elements.ImportMultiValue
+    delimiter = ''
+    delimiter_options = [
+        ('/', '/'),
+        (' ', 'space'),
+        ('\t', 'tab'),
+        (',', 'comma'),
+    ]
+
+    def __init__(self, importrule):
+        ActionSource.__init__(self, importrule)
+        if self.initial:
+            self.delimiter = importrule.delimiter
+
+    def to_element(self, name):
+        if self.src and self.src != CHOOSE:
+            return self.ctor(name, self.src, self.delimiter)
+        else:
+            return elements.ImportIgnore(name)
+
+
+class ActionFixed(Action):
+    action_name = 'fixed'
+    desc = 'Set value'
+    ctor = elements.ImportFixed
+    value = ''
+
+    def __init__(self, importrule):
+        Action.__init__(self, importrule)
+        if self.initial:
+            self.value = importrule.value
+
+    def to_element(self, name):
+        return self.ctor(name, self.value)
+
+
+class ActionIgnore(Action):
+    action_name = 'ignore'
+    desc = 'Ignore'
+    ctor = elements.ImportIgnore
+
+    def __init__(self, importrule):
+        Action.__init__(self, importrule)
+
+    def to_element(self, name):
+        return self.ctor(name)
+
+
+class TranslateBase(object):
+    type = None
+    match = ''
+    to = ''
+    ignorecase = 'True'
+
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+
+    def from_element(cls, element):
+        return cls(match=element.match,
+                   to=element.to,
+                   ignorecase=str(element.ignorecase))
+    from_element = classmethod(from_element)
+
+
+class Translate(TranslateBase):
+    type = 'Translate'
+
+    def to_element(self):
+        return elements.Translate(self.match, self.to, 
+                                  self.ignorecase == 'True')
+
+
+class RegExp(TranslateBase):
+    type = 'RegExp'
+
+    def to_element(self):
+        return elements.RegExp(self.match, self.to, 
+                                  self.ignorecase == 'True')
+
+
+class EditField(object):
+    date_formats = [
+        'YYYY-MM-DD',
+        'YYYY-MM-DD hh:mm:ss',
+        'YYYYMMDD',
+        'DD/MM/YY',
+        'DD/MM/YY hh:mm:ss',
+        'DD/MM/YYYY',
+        'DD/MM/YYYY hh:mm:ss',
+        'MM/DD/YY',
+        'MM/DD/YY hh:mm:ss',
+        'MM/DD/YYYY',
+        'MM/DD/YYYY hh:mm:ss',
+    ]
+    date_format = ''
+    date_format_other = ''
+    case_options = [
+        ('unchanged', 'Unchanged'),
+        ('upper', 'Upper'),
+        ('lower', 'Lower'),
+        ('title', 'Title'),
+        ('capitalize', 'Capitalise'),
+    ]
+    case = 'unchanged'
+
+    def __init__(self, group, name, label, importrule, 
+                 field_options=None,
+                 is_datefield=False, is_filterable=True, 
+                 is_dob=False, is_set=False, ignore_case=False):
+        self.group = group
+        self.name = name
+        self.label = label
+        self.importrule = importrule
+        self.is_datefield = is_datefield
+        self.is_filterable = is_filterable
+        self.is_set = is_set
+        self.ignore_case = ignore_case
+        if is_dob:
+            action_cls = (ActionAgeSource, ActionSource)
+        elif is_set:
+            action_cls = (ActionMultiValue,)
+        else:
+            action_cls = (ActionSource,)
+        action_cls = action_cls + (ActionFixed, ActionIgnore)
+        self.actions = [cls(importrule) for cls in action_cls]
+        self.selected = self.actions[0]
+        for action in self.actions:
+            if isinstance(importrule, action.ctor):
+                self.selected = action
+        self.selected.selected = True
+        self.field_options = field_options
+        self.translations = []
+        for translation in importrule.translations:
+            if isinstance(translation, elements.Date):
+                for fmt in self.date_formats:
+                    if fmt == translation.format:
+                        self.date_format = fmt
+                        break
+                else:
+                    self.date_format_other = translation.format
+            elif isinstance(translation, elements.Case) and not self.ignore_case:
+                self.case = translation.mode
+            elif isinstance(translation, elements.Translate):
+                self.translations.append(Translate.from_element(translation))
+            elif isinstance(translation, elements.RegExp):
+                self.translations.append(RegExp.from_element(translation))
+
+    def demog(cls, demogfield, importrule):
+        """
+        Ctor from demogfield
+        """
+        is_datefield = isinstance(demogfield,demogfields.DatetimeBase)
+        field_options = None
+        if demogfield.optionexpr is not None:
+            field_options = demogfield.optionexpr()
+        self = cls(None, demogfield.name, demogfield.label, importrule,
+                   is_datefield=is_datefield,
+                   is_dob=(demogfield.name == 'DOB'),
+                   field_options=field_options)
+        return self
+    demog = classmethod(demog)
+
+    def form(cls, form, input, importrule):
+        """
+        Ctor from form input
+        """
+        is_datefield = input.input_group and input.input_group.startswith('Date')
+        try:
+            get_choices = input.get_choices
+        except AttributeError:
+            field_options = None
+        else:
+            field_options = [v for v in get_choices() if v[0]]
+        self = cls(form, input.column, input.label or input.column, importrule,
+                   is_datefield=is_datefield,
+                   is_set=input.type_name.startswith('Checkboxes'),
+                   ignore_case=input.type_name.startswith('Checkboxes'),
+                   field_options=field_options)
+        return self
+    form = classmethod(form)
+
+    def src_fields(self, dataimp_src):
+        fields = list(dataimp_src.preview.col_names)
+        fields.sort()
+        fields.insert(0, CHOOSE)
+        return fields
+
+    def colvalues(self, dataimp_src):
+        if not self.selected.src:
+            return None
+        delimiter = getattr(self.selected, 'delimiter', None)
+        values = dataimp_src.preview.colvalues(self.selected.src)
+        if values:
+            have = set()
+            element = self.to_element()
+            for value in values:
+                if delimiter:
+                    for value in value.split(delimiter):
+                        if value:
+                            have.add(value)
+                elif value:
+                    have.add(value)
+            values = list(have)
+            values.sort()
+        return values
+
+    def preview(self, dataimp_src):
+        src_col = getattr(self.selected, 'src', None)
+        if not src_col:
+            return []
+        preview = (dataimp_src.preview.colvalues(src_col)
+                   or dataimp_src.preview.colpreview(src_col))
+        if preview:
+            preview = preview[:15]
+        return preview
+
+    def get_missing_field_options(self, colvalues=None):
+        want = set()
+        for value, label in self.field_options:
+            if self.ignore_case:
+                value = value.lower()
+            want.add(value)
+        have = set()
+        if colvalues:
+            element = self.to_element()
+            for value in colvalues:
+                value = element.translate(value)
+                if value:
+                    if isinstance(value, set):
+                        have.update(value)
+                    else:
+                        have.add(value)
+        else:
+            for translation in self.translations:
+                if translation.match:
+                    have.add(translation.match)
+        if self.ignore_case:
+            have = set([value.lower() for value in have])
+        #print >> sys.stderr, 'HERE', colvalues, have, want
+        return have - want
+
+    def get_field_options_sorted(self, colvalues=None):
+        options = list(self.get_missing_field_options(colvalues))
+        options.sort()
+        return options
+
+    def add_field_opts(self, colvalues=None):
+        inp = [(-len(o), o) 
+                   for o in self.get_missing_field_options(colvalues)]
+        inp.sort()
+        for l, inp in inp:
+            to = ''
+            for opt, label in self.field_options:
+                if opt.lower() == inp.lower():
+                    to = opt
+            self.translations.append(Translate(match=inp, to=to))
+
+    def set_action(self, action_name):
+        for action in self.actions:
+            action.selected = (action.action_name == action_name)
+            if action.selected:
+                self.selected = action
+
+    def add_translate(self, regexp=False):
+        cls = Translate
+        if regexp:
+            cls = RegExp
+        if self.translations:
+            proto = self.translations[-1]
+            translate = cls(ignorecase=proto.ignorecase)
+        else:
+            translate = cls()
+        self.translations.append(translate)
+
+    def del_translate(self, n):
+        del self.translations[n]
+
+    def to_element(self):
+        element = self.selected.to_element(self.name)
+        for translation in self.translations:
+            if translation.match and translation.to != CHOOSE:
+                element.translation(translation.to_element())
+        if self.case != 'unchanged':
+            element.translation(elements.Case(self.case))
+        if self.date_format_other:
+            element.translation(elements.Date(self.date_format_other))
+        elif self.date_format:
+            element.translation(elements.Date(self.date_format))
+        return element
+
+    def trial_translate(self, dataimp_src):
+        values = self.preview(dataimp_src)
+        element = self.to_element()
+        for value in values:
+            element.translate(value)
+
+
+class FieldView(object):
+    def __init__(self, name, label, importrule):
+        self.name = name
+        self.label = label
+        if importrule is None:
+            self.action_desc = 'Not defined'
+        else:
+            self.action_desc = ', '.join(importrule.desc())
+
+
+class GroupView(list):
+    def __init__(self, name, label):
+        self.name = name
+        self.label = label
+        self.add_options = [('', CHOOSE)]
+
+    def add_option(self, name, label):
+        self.add_options.append(('%s.%s' % (self.name, name), label))
+
+    def add_field(self, name, label, importrule):
+        self.append(FieldView(name, label, importrule))
+
+
+class RulesView(list):
+    skip_fields = set(('case_definition', 'case_id', 'deleted'))
+
+    def __init__(self, importrules, demogfields, syndrome, dataimp_src=None):
+        self.add_options = []           # Available forms
+        unused_cols = set()             # Unused source fields
+
+        if dataimp_src and dataimp_src.preview:
+            unused_cols.update(dataimp_src.preview.col_names)
+
+        def add_field(node, group, name, label):
+            importrule = node.get(name)
+            if (importrule is None 
+                    or isinstance(importrule, elements.ImportIgnore)):
+                group.add_option(name, label)
+            else:
+                group.add_field(name, label, importrule)
+                col = getattr(importrule, 'src', None)
+                if col:
+                    unused_cols.discard(col)
+                    col = getattr(importrule, 'age', None)
+                    if col:
+                        unused_cols.discard(col)
+
+        fields = []
+        for field in demogfields:
+            if (field.name in self.skip_fields
+                or not (field.show_case or field.show_person 
+                        or field.show_search or field.show_result)):
+                continue
+            fields.append((field.label, field.name))
+        fields.sort()
+        group = GroupView('', 'Demographic fields')
+        for label, name in fields:
+            add_field(importrules, group, name, label)
+        self.append(group)
+
+        for info in syndrome.all_form_info():
+            try:
+                form_rules = importrules.get_form(info.name)
+            except KeyError:
+                self.add_options.append((info.name, info.label))
+            else:
+                form = info.load()
+                fields = []
+                for input in form.get_inputs():
+                    fields.append((input.label or input.column, input.column))
+                fields.sort()
+                group = GroupView(info.name, info.label or info.name)
+                for label, name in fields:
+                    add_field(form_rules, group, name, label)
+                self.append(group)
+
+        self.unused_cols = list(unused_cols)
+        self.unused_cols.sort()
+
+        # only allow one form?
+        #if importrules.has_forms():
+        #    self.add_options = []
+        if self.add_options:
+            self.add_options.insert(0, ('', CHOOSE))
+
+
+class Editor(object):
+    encodings = (
+        'ascii',
+        'utf-8',
+        'utf-16',
+        'latin1',
+        'iso-8859-1',
+        'iso-8859-2',
+        'iso-8859-3',
+        'iso-8859-4',
+        'iso-8859-5',
+        'iso-8859-6',
+        'iso-8859-7',
+        'iso-8859-8',
+        'iso-8859-9',
+        'iso-8859-10',
+        'iso-8859-11',
+        'iso-8859-12',
+        'iso-8859-13',
+        'iso-8859-14',
+        'iso-8859-15',
+        'unknown',
+    )
+    fieldseps = ('comma', 'tab', 'vertical bar', 'unknown')
+    from_fieldsep = {
+        '\t': 'tab',
+        ',': 'comma',
+        '|': 'vertical bar',
+    }
+    to_fieldsep = {
+        'tab': '\t',
+        'comma': ',',
+        'vertical bar': '|',
+    }
+
+    def __init__(self, syndrome_id, def_id, importrules):
+        self.syndrome_id = syndrome_id
+        self.def_id = def_id
+        self.orig_importrules = importrules
+        self.set_rules(importrules)
+
+    def set_rules(self, importrules):
+        self.importrules = copy.deepcopy(importrules)
+        self.encoding = self.importrules.encoding
+        if self.encoding not in self.encodings:
+            self.encoding = 'unknown'
+        self.fieldsep = self.from_fieldsep.get(self.importrules.fieldsep, 
+                                               'unknown')
+
+    def load_check(self, msgs):
+        for form in self.importrules.forms():
+            info = self.syndrome().form_info(form.name)
+            if info is None:
+                msgs.msg('err', 'Form %r no longer associated with this %s'%
+                         (form.name, config.syndrome_label))
+            elif info.version != form.version:
+                msgs.msg('warn', 'Form %r definition has been updated - '
+                                 'check import rules' % info.label)
+                form.version = info.version
+
+    def update_rules(self):
+        if self.encoding != 'unknown':
+            self.importrules.encoding = self.encoding
+        if self.fieldsep != 'unknown':
+            self.importrules.fieldsep = self.to_fieldsep[self.fieldsep]
+
+    def syndrome(self):
+        return syndrome.syndromes[self.syndrome_id]
+
+    def demogfields(self):
+        return demogfields.get_demog_fields(globals.db, self.syndrome_id)
+
+    def view(self, dataimp_src=None):
+        return RulesView(self.importrules, self.demogfields(),
+                         self.syndrome(), dataimp_src)
+
+    def add_field(self, field, src=None):
+        group, field = field.split('.', 1)
+        if group:
+            rules = self.importrules.get_form(group)
+        else:
+            rules = self.importrules
+        rules.add(elements.ImportSource(field, src))
+        return group, field
+
+    def add_form(self, name):
+        info = self.syndrome().form_info(name)
+        self.importrules.new_form(info.name, info.version)
+
+    def del_form(self, name):
+        self.importrules.del_form(name)
+
+    def edit_field(self, group, name):
+        if group:
+            form_rules = self.importrules.get_form(group)
+            form = self.syndrome().form_info(group).load()
+            input = form.columns.find_input(name)
+            return EditField.form(group, input, form_rules[name])
+        else:
+            demogfield = self.demogfields().field_by_name(name)
+            return EditField.demog(demogfield, self.importrules[name])
+
+    def save_edit_field(self, edit_field):
+        if edit_field.group:
+            rules = self.importrules.get_form(edit_field.group)
+        else:
+            rules = self.importrules
+        rules.add(edit_field.to_element())
+
+    def has_changed(self):
+        self.update_rules()
+        return self.orig_importrules != self.importrules
+
+    def revert(self):
+        self.set_rules(self.orig_importrules)
+
+    def rules_xml(self):
+        f = StringIO()
+        xmlsave.xmlsave(f, self.importrules)
+        return f.getvalue()
+
+    def save(self):
+        self.update_rules()
+        if self.def_id is None:
+            row = None
+        else:
+            query = globals.db.query('import_defs', for_update=True)
+            query.where('syndrome_id = %s', self.syndrome_id)
+            query.where('import_defs_id = %s', self.def_id)
+            row = query.fetchone()
+        # New (or missing)?
+        if row is None:
+            row = globals.db.new_row('import_defs')
+            row.syndrome_id = self.syndrome_id
+        # Check there isn't a name conflict
+        query = globals.db.query('import_defs', for_update=True)
+        query.where('syndrome_id = %s', self.syndrome_id)
+        if self.def_id is not None:
+            query.where('import_defs_id != %s', self.def_id)
+        query.where('name = %s', self.importrules.name)
+        if query.fetchcols('import_defs_id'):
+            raise Error('Name %r already used' % self.importrules.name)
+        # Update
+        row.xmldef = self.rules_xml()
+        row.name = self.importrules.name
+        row.db_update()
+        self.def_id = row.import_defs_id
+        self.orig_importrules = copy.deepcopy(self.importrules)
+
+    def delete(self):
+        if self.def_id is not None:
+            query = globals.db.query('import_defs')
+            query.where('syndrome_id = %s', self.syndrome_id)
+            query.where('import_defs_id = %s', self.def_id)
+            query.delete()
+            self.def_id = None
+
+    def available(self):
+        query = globals.db.query('import_defs', order_by='name')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        return query.fetchcols(('import_defs_id', 'name'))
+
+
+def new(syndrome_id):
+    return Editor(syndrome_id, None, elements.ImportRules(''))
+
+
+def load_file(msgs, syndrome_id, def_id, f):
+    try:
+        editor = Editor(syndrome_id, def_id, xmlload.xmlload(f))
+    except xmlload.ParseError, e:
+        raise Error('Unable to load import rules: %s' % e)
+    else:
+        editor.load_check(msgs)
+        return editor
+
+
+def load(msgs, syndrome_id, def_id):
+    query = globals.db.query('import_defs')
+    query.where('syndrome_id = %s', syndrome_id)
+    query.where('import_defs_id = %s', def_id)
+    row = query.fetchone()
+    if row is None:
+        raise Error('Import definition not found')
+    return load_file(msgs, syndrome_id, def_id, StringIO(row.xmldef))
diff --git a/casemgr/dataimp/elements.py b/casemgr/dataimp/elements.py
new file mode 100644
index 0000000..52affde
--- /dev/null
+++ b/casemgr/dataimp/elements.py
@@ -0,0 +1,306 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+This is an abstract representation of a set of import rules
+"""
+
+import sys
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import datetime
+from casemgr import globals
+from casemgr.dataimp.common import DataImpElementError as Error
+
+debug = False
+
+class ElementBase(object):
+
+    def __eq__(self, other):
+        if self.__class__ is not other.__class__:
+            return False
+        if debug and self.__dict__ != other.__dict__:
+            print >> sys.stderr, 'EQ', self.__class__.__name__
+            keys = set(self.__dict__) | set(other.__dict__)
+            for key in keys:
+                if key not in self.__dict__:
+                    print >> sys.stderr, '  %s not in self' % key
+                elif key not in other.__dict__:
+                    print >> sys.stderr, '  %s not in other' % key
+                elif self.__dict__[key] != other.__dict__[key]:
+                    print >> sys.stderr, '  %s not equal' % key
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class TranslationBase(ElementBase):
+    pass
+
+
+class Date(TranslationBase):
+    
+    def __init__(self, format):
+        self.format = format
+        self.parse = datetime.fmt_parser(format, age_years=True)
+
+    def translate(self, value):
+        if value:
+            try:
+                return self.parse(value)
+            except datetime.Error:
+                raise Error('date/time %r does not match format %r' %
+                                 (value, self.format))
+
+    def desc(self):
+        return ['date format %r' % self.format]
+
+
+class Translate(TranslationBase):
+    
+    def __init__(self, match, to, ignorecase=False):
+        self.match = match
+        self.to = to
+        self.ignorecase = ignorecase
+
+    def translate(self, value):
+        if value:
+            if self.ignorecase:
+                if value.lower() == self.match.lower():
+                    value = self.to
+            else:
+                if value == self.match:
+                    value = self.to
+            return value
+
+    def desc(self):
+        return ['translate %r to %r' % (self.match, self.to)]
+
+
+class RegExp(TranslationBase):
+    
+    def __init__(self, match, to, ignorecase=False):
+        self.match = match
+        self.to = to
+        self.ignorecase = ignorecase
+        self.match_re = None
+
+    def __deepcopy__(self, memo):
+        # Work-around a problem in some versions of python. _sre.SRE_Pattern
+        # has a __deepcopy__ method that raises TypeError: cannot deepcopy this
+        # pattern object, but bugs in 2.3.5 and 2.4.5(?) mask this method,
+        # allowing the SRE_Pattern to be deepcopied via the pickle machinery.
+        return RegExp(self.match, self.to, self.ignorecase)
+
+    def translate(self, value):
+        try:
+            if self.match_re is None:
+                if self.ignorecase:
+                    self.match_re = re.compile(self.match, re.IGNORECASE)
+                else:
+                    self.match_re = re.compile(self.match)
+            if value:
+                return self.match_re.sub(self.to, value)
+        except re.error, e:
+            raise Error('regexp translation: %s' % e)
+
+    def desc(self):
+        return ['regexp %r to %r' % (self.match, self.to)]
+
+
+class Case(TranslationBase):
+    
+    def __init__(self, mode):
+        self.mode = mode
+
+    def translate(self, value):
+        if value:
+            if self.mode == 'lower':
+                return value.lower()
+            elif self.mode == 'upper':
+                return value.upper()
+            elif self.mode == 'title':
+                return value.title()
+            elif self.mode == 'capitalize':
+                return value.capitalize()
+            else:
+                return value
+
+    def desc(self):
+        return ['case %r' % (self.mode,)]
+
+
+class ImportColumnBase(ElementBase):
+
+    def __init__(self, field):
+        self.field = field
+        self.translations = []
+
+    def translation(self, xlate):
+        self.translations.append(xlate)
+
+    def translate(self, value):
+        for translation in self.translations:
+            value = translation.translate(value)
+        return value
+
+    def desc(self):
+        desc = []
+        for t in self.translations:
+            desc.extend(t.desc())
+        return desc
+
+
+class ImportSource(ImportColumnBase):
+    
+    def __init__(self, field, src=None, pos=None, name=None):
+        ImportColumnBase.__init__(self, field)
+        self.src = src or pos or name  # Legacy
+
+    def desc(self):
+        desc = ['source column %r' % self.src]
+        desc.extend(ImportColumnBase.desc(self))
+        return desc
+
+
+class ImportAgeSource(ImportColumnBase):
+    
+    def __init__(self, field, src=None, age=None):
+        ImportColumnBase.__init__(self, field)
+        self.src = src
+        self.age = age
+
+    def desc(self):
+        desc = ['age/dob column %r' % self.src]
+        if self.age:
+            desc.append('age %r' % self.age)
+        desc.extend(ImportColumnBase.desc(self))
+        return desc
+
+
+class ImportMultiValue(ImportSource):
+
+    def __init__(self, field, src=None, delimiter=None):
+        ImportSource.__init__(self, field, src)
+        self.delimiter = delimiter
+
+    def desc(self):
+        desc = ['multivalue column %r, delimiter %r' % (self.src, self.delimiter)]
+        desc.extend(ImportColumnBase.desc(self))
+        return desc
+
+    def translate(self, value):
+        result = set()
+        if value:
+            for value in value.split(self.delimiter):
+                for translation in self.translations:
+                    value = translation.translate(value)
+                if value:
+                    result.add(value)
+        return result
+
+
+class ImportFixed(ImportColumnBase):
+    def __init__(self, field, value):
+        ImportColumnBase.__init__(self, field)
+        self.value = value        
+
+    def desc(self):
+        return ['set to %r' % self.value]
+
+
+class ImportIgnore(ImportColumnBase):
+
+    def desc(self):
+        return ['ignore']
+
+
+class RuleSetBase(ElementBase):
+    def __init__(self):
+        self.rules_by_name = {}
+
+    def __nonzero__(self):
+        return bool(self.rules_by_name)
+
+    def __getitem__(self, name):
+        return self.rules_by_name[name]
+
+    def get(self, name, default=None):
+        return self.rules_by_name.get(name, default)
+
+    def __contains__(self, name):
+        return name in self.rules_by_name
+
+    def __iter__(self):
+        names = self.rules_by_name.keys()
+        names.sort()
+        for name in names:
+            yield self.rules_by_name[name]
+
+    def add(self, col):
+        self.rules_by_name[col.field] = col
+
+    def drop(self, field):
+        self.rules_by_name.pop(field, None)
+
+
+class ImportForm(RuleSetBase):
+    def __init__(self, name, version):
+        RuleSetBase.__init__(self)
+        self.name = name
+        self.version = version
+
+
+class ImportRules(RuleSetBase):
+    def __init__(self, name, mode='named', encoding='utf-8', fieldsep=',',
+                 srclabel='import', conflicts='ignore'):
+        RuleSetBase.__init__(self)
+        self.name = name
+        self.mode = mode
+        self.encoding = encoding
+        self.fieldsep = fieldsep
+        self.srclabel = srclabel
+        self.conflicts = conflicts
+        self._forms = {}
+
+    def has_forms(self):
+        return bool(self._forms)
+
+    def new_form(self, name, version):
+        self._forms[name] = form = ImportForm(name, version)
+        return form
+
+    def del_form(self, name):
+        del self._forms[name]
+
+    def get_form(self, name):
+        return self._forms[name]
+
+    def forms(self):
+        # XXX do we really need to sort? We need a stable order in the XML
+        # representation for testing purposes, but the sort could be done
+        # in xmlsave.
+        anno_forms = self._forms.items()
+        anno_forms.sort()
+        return [form for name, form in anno_forms]
+
diff --git a/casemgr/dataimp/xmlload.py b/casemgr/dataimp/xmlload.py
new file mode 100644
index 0000000..c126974
--- /dev/null
+++ b/casemgr/dataimp/xmlload.py
@@ -0,0 +1,119 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import xmlparse
+from casemgr.dataimp import elements
+
+ParseError = xmlparse.ParseError
+
+class ImportXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'importrules'
+
+    class _Translate(xmlparse.Node):
+        
+        def end_element(self, parent):
+            parent.rule.translations.append(self.ctor(**self.attrs))
+
+
+    class Date(_Translate):
+        permit_attrs = ('format',)
+        ctor = elements.Date
+
+
+    class Translate(_Translate):
+        permit_attrs = ('match', 'to', 'ignorecase:bool')
+        ctor = elements.Translate
+
+
+    class RegExp(_Translate):
+        permit_attrs = ('match', 'to', 'ignorecase:bool')
+        ctor = elements.RegExp
+
+
+    class Case(_Translate):
+        permit_attrs = ('mode',)
+        ctor = elements.Case
+
+
+    class _Rule(xmlparse.Node):
+        __slots__ = ('rule',)
+        subtags = ('date', 'translate', 'regexp', 'case')
+
+        def start_element(self, parent):
+            self.rule = self.ctor(**self.attrs)
+
+        def end_element(self, parent):
+            parent.node.add(self.rule)
+
+
+    class Source(_Rule):
+        permit_attrs = ('field', 'src', 'name', 'pos')
+        ctor = elements.ImportSource
+
+    # Legacy
+    class Named(Source): pass
+    class Positional(Source): pass
+
+    class AgeSource(Source):
+        permit_attrs = ('field', 'src', 'age')
+        ctor = elements.ImportAgeSource
+
+    class MultiValue(Source):
+        permit_attrs = ('field', 'src', 'delimiter')
+        ctor = elements.ImportMultiValue
+
+    class Fixed(_Rule):
+        ctor = elements.ImportFixed
+        permit_attrs = ('field', 'value')
+
+
+    class Ignore(_Rule):
+        ctor = elements.ImportIgnore
+        permit_attrs = ('field',)
+
+
+    class Form(xmlparse.Node):
+        __slots__ = ('node')
+        permit_attrs = (
+            'name', 'version:int',
+        )
+        subtags = (
+            'source', 'agesource', 'multivalue', 'fixed', 'ignore',
+        )
+
+        def start_element(self, parent):
+            self.node = parent.node.new_form(**self.attrs)
+
+
+    class ImportRules(xmlparse.Node):
+        __slots__ = 'node',
+        permit_attrs = (
+            'name', 'mode', 'encoding', 'fieldsep', 'srclabel', 'conflicts',
+        )
+        subtags = (
+            'form', 'source', 'agesource', 'named', 'positional', 
+            'fixed', 'ignore',
+        )
+
+        def start_element(self, parent):
+            self.node = elements.ImportRules(**self.attrs)
+
+
+def xmlload(f):
+    return ImportXMLParse().parse(f).node
diff --git a/casemgr/dataimp/xmlsave.py b/casemgr/dataimp/xmlsave.py
new file mode 100644
index 0000000..b0432a8
--- /dev/null
+++ b/casemgr/dataimp/xmlsave.py
@@ -0,0 +1,141 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+from cocklebur.xmlwriter import XMLwriter
+import elements
+
+translation_type_to_xml = {}
+
+def date(x, node):
+    e = x.push('date')
+    e.attr('format', node.format)
+    x.pop()
+
+translation_type_to_xml[elements.Date] = date
+
+def translate(x, node):
+    e = x.push('translate')
+    e.attr('match', node.match)
+    e.attr('to', node.to)
+    if node.ignorecase:
+        e.attr('ignorecase', 'yes')
+    x.pop()
+
+translation_type_to_xml[elements.Translate] = translate
+
+def regexp(x, node):
+    e = x.push('regexp')
+    e.attr('match', node.match)
+    e.attr('to', node.to)
+    if node.ignorecase:
+        e.attr('ignorecase', 'yes')
+    x.pop()
+
+translation_type_to_xml[elements.RegExp] = regexp
+
+def case(x, node):
+    e = x.push('case')
+    e.attr('mode', node.mode)
+    x.pop()
+
+translation_type_to_xml[elements.Case] = case
+
+def translations(x, node):
+    for child in node.translations:
+        assert isinstance(child, elements.TranslationBase)
+        translation_type_to_xml[child.__class__](x, child)
+
+import_type_to_xml = {}
+
+def source(x, node):
+    e = x.push('source')
+    e.attr('field', node.field)
+    e.attr('src', node.src)
+    translations(x, node)
+    x.pop()
+
+import_type_to_xml[elements.ImportSource] = source
+
+def agesource(x, node):
+    e = x.push('agesource')
+    e.attr('field', node.field)
+    e.attr('src', node.src)
+    if node.age:
+        e.attr('age', node.age)
+    translations(x, node)
+    x.pop()
+
+import_type_to_xml[elements.ImportAgeSource] = agesource
+
+def multivalue(x, node):
+    e = x.push('multivalue')
+    e.attr('field', node.field)
+    e.attr('src', node.src)
+    e.attr('delimiter', node.delimiter or '')
+    translations(x, node)
+    x.pop()
+
+import_type_to_xml[elements.ImportMultiValue] = multivalue
+
+
+def fixed(x, node):
+    e = x.push('fixed')
+    e.attr('field', node.field)
+    e.attr('value', node.value)
+    translations(x, node)
+    x.pop()
+
+import_type_to_xml[elements.ImportFixed] = fixed
+    
+
+def ignore(x, node):
+    e = x.push('ignore')
+    e.attr('field', node.field)
+    x.pop()
+
+import_type_to_xml[elements.ImportIgnore] = ignore
+
+
+def form(x, node):
+    if node:
+        e = x.push('form')
+        e.attr('name', node.name)
+        e.attr('version', node.version)
+        for child in node:
+            assert isinstance(child, elements.ImportColumnBase)
+            import_type_to_xml[child.__class__](x, child)
+        x.pop()
+
+import_type_to_xml[elements.ImportForm] = form
+
+
+def xmlsave(f, node):
+    x = XMLwriter(f)
+    e = x.push('importrules')
+    e.attr('name', node.name)
+    e.attr('mode', node.mode)
+    e.attr('encoding', node.encoding)
+    e.attr('fieldsep', node.fieldsep)
+    e.attr('srclabel', node.srclabel)
+    e.attr('conflicts', node.conflicts)
+    for child in node:
+        import_type_to_xml[child.__class__](x, child)
+    for child in node.forms():
+        import_type_to_xml[child.__class__](x, child)
+    x.pop()
diff --git a/casemgr/demogfields.py b/casemgr/demogfields.py
new file mode 100644
index 0000000..ef8486f
--- /dev/null
+++ b/casemgr/demogfields.py
@@ -0,0 +1,1108 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard Modules
+import time
+# Application Modules
+import config
+from cocklebur import datetime, agelib, languages, countries, utils
+from casemgr import person, casestatus, caseassignment, casetags, \
+                    syndrome, addressstate
+
+TTL = 10 * 60
+DEMOG_TABLE = 'syndrome_demog_fields'
+
+# NOTE If adding more contexts, add columns to syndrome_demog_fields table.
+contexts = 'case', 'form', 'search', 'person', 'result'
+_showattrs = ['show_' + c for c in contexts]
+
+entity_table = {
+    None: None,
+    'case': 'cases', 
+    'person': 'persons'
+}
+demog_classes = []
+
+class DemogFieldMeta(type):
+    def __init__(cls, name, bases, attrs):
+        type.__init__(cls, name, bases, attrs)
+        cls.table = entity_table[cls.entity]
+        if 'name' in attrs:
+            demog_classes.append(cls)
+
+def _copyattrs(dst, src):
+    if src.label:
+        dst.label = src.label
+    for varname in _showattrs:
+        setattr(dst, varname, bool(getattr(src, varname, True)))
+
+def _cmpattrs(dst, src):
+    if src.label != dst.label:
+        return False
+    for varname in _showattrs:
+        if bool(getattr(src, varname)) != bool(getattr(dst, varname)):
+            return False
+    return True
+
+def _getpathattr(ns, path):
+    try:
+        return utils.nsgetattr(ns, path)
+    except AttributeError:
+        return None
+
+
+class DemogSaved(object):
+
+    def __init__(self, row):
+        _copyattrs(self, row)
+
+
+class DemogField(object):
+    __metaclass__ = DemogFieldMeta
+#    db_fields = 'label', 'show_case', 'show_form'
+    hideable = True
+    show_case = True
+    show_form = True
+    show_search = True
+    show_person = True
+    show_result = True
+    optionexpr = None
+    disabled = False
+    disabled_form = True
+    field = None
+    render = None
+    section = None
+    syndrome_id = None
+    context = None
+    entity = None
+
+    def __init__(self, syndrome_id, row=None, save_initial=False):
+        self.syndrome_id = syndrome_id
+        if row is not None:
+            _copyattrs(self, row)
+        if save_initial:
+            self.initial = DemogSaved(self)
+
+    def allow_field(self, context):
+        return hasattr(self, 'field_' + context)
+
+    def show(self, context):
+        """ Should the field appear in the given /context/? """
+        return (self.allow_field(context) 
+                and getattr(self, 'show_' + context, True))
+
+    def outtrans(self, ns):
+        return _getpathattr(ns, self.field or self.name)
+
+    def summary(self, ns):
+        value = self.outtrans(ns)
+        if value:
+            return '%s: %s' % (self.label, value)
+
+    def context_field(self, context, disabled=False):
+        """ Make a context-specific copy of this field """
+        context_field = self.__class__(self.syndrome_id)
+        context_field.syndrome_id = self.syndrome_id
+        context_field.context = context
+        for attr in ('field', 'label', 'render', 'disabled', 'section'):
+            value = getattr(self, attr + '_' + context, None)
+            if value is None:
+                value = getattr(self, attr)
+            if attr == 'disabled' and disabled:
+                value = disabled
+            setattr(context_field, attr, value)
+        return context_field
+
+    ### Methods from this point on are only used when editing fields
+    def has_changed(self):
+        return not _cmpattrs(self, self.initial)
+
+    def reset(self):
+        del self.label
+        for varname in _showattrs:
+            try:
+                delattr(self, varname)
+            except AttributeError:
+                pass
+        _copyattrs(self, self.__class__)
+
+    def set(self, state):
+        for varname in _showattrs:
+            setattr(self, varname, state)
+
+    def update(self, row, common):
+        all_defaults = True
+        if not self.label or self.label == common.label:
+            row.label = None
+        else:
+            row.label = self.label
+            all_defaults = False
+        for varname in _showattrs:
+            commonvalue = getattr(common, varname)
+            selfvalue = bool(getattr(self, varname, commonvalue))
+            setattr(row, varname, selfvalue)
+            if selfvalue != commonvalue:
+                all_defaults = False
+        if all_defaults:
+            row.db_delete()
+        else:
+            row.db_update(refetch=False)
+
+
+class SelDemogField(DemogField):
+    render = 'select'
+
+    def optionexpr(self):
+        optionexpr = self._optionexpr()
+        if self.context in ('search', 'person'):
+            assert not optionexpr[0][0], 'optionexpr[0][0] is %r, not null\n%r' % (optionexpr[0][0], optionexpr)
+            optionexpr = list(optionexpr)
+            optionexpr[0:1] = [
+                ('', 'Any'),
+                ('!', optionexpr[0][1]),
+            ]
+        return optionexpr
+
+    def outtrans(self, ns):
+        value = _getpathattr(ns, self.field or self.name)
+        if value:
+            map = dict(self.optionexpr())
+            return map.get(value, value)
+
+
+class DemogStateBase(SelDemogField):
+
+    def _optionexpr(self):
+        return addressstate.optionexpr()
+
+
+class DatetimeBase(DemogField):
+    render = 'datetimeinput'
+
+    def format(self):
+        return datetime.mx_parse_datetime.format
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value:
+            return value.strftime(self.format())
+
+
+class DemogCountryBase(SelDemogField):
+    show_result = False
+
+    def _optionexpr(self):
+        return countries.country_optionexpr
+
+
+class DemogInterpreterRequired(SelDemogField):
+    name = 'interpreter_req'
+    label = 'Interpreter'
+    show_result = False
+    field_case = field_form = 'case.person.interpreter_req'
+    field_search = field_person = 'search.person.interpreter_req'
+    field_result = 'interpreter_req'
+    entity = 'person'
+
+    def _optionexpr(self):
+        return languages.language_optionexpr
+
+
+class DemogIndigenous(SelDemogField):
+    name = 'indigenous_status'
+    label = 'Indigenous status'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.indigenous_status'
+    field_search = field_person = 'search.person.indigenous_status'
+    field_result = 'indigenous_status'
+    entity = 'person'
+
+    def _optionexpr(self):
+        return person.indigenous_values
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value:
+            return person.indigenous_map.get(value, '')
+
+    def summary(self, ns):
+        return self.outtrans(ns)
+
+
+class DemogCaseDefinition(DemogField):
+    name = 'case_definition'
+    label = config.syndrome_label
+    render = 'value'
+    render_search = 'select_syndrome'
+    show_person = False
+    field_case = 'case.syndrome.name'
+    field_form = 'case.syndrome.syndrome_id'
+    field_search = 'search.search_syndrome_id'
+    field_result = 'syndrome_id'
+    section = 'id'
+    entity = 'case'
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value and value != 'Any':
+            return syndrome.syndromes[value].name
+
+    def summary(self, ns):
+        return self.outtrans(ns)
+
+
+class DemogCaseStatus(SelDemogField):
+    name = 'case_status'
+    label = 'Status'
+    show_person = False
+    field_case = field_form = 'case.case_row.case_status'
+    field_search = 'search.case_status'
+    field_result = 'case_status'
+    section = 'id'
+    entity = 'case'
+
+    def _optionexpr(self):
+        if self.syndrome_id is not None:
+            args = [self.syndrome_id]
+        else:
+            args = []
+        return casestatus.optionexpr(*args)
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value:
+            return casestatus.get_label(self.syndrome_id, value)
+
+
+class DemogLocalId(DemogField):
+    name = 'local_case_id'
+    label = 'Local ID'
+    render = 'textinput'
+    field_case = field_form = 'case.case_row.local_case_id'
+    field_search = field_person = 'search.local_case_id'
+    field_result = 'local_case_id'
+    section = 'id'
+    entity = 'case'
+
+
+class DemogCaseAssignment(SelDemogField):
+    name = 'case_assignment'
+    label = 'Case Assignment'
+    show_person = False
+    field_case = field_form = 'case.case_row.case_assignment'
+    field_search = 'search.case_assignment'
+    field_result = 'case_assignment'
+    section = 'id'
+    entity = 'case'
+
+    def _optionexpr(self):
+        if self.syndrome_id is not None:
+            args = [self.syndrome_id]
+        else:
+            args = []
+        return caseassignment.optionexpr(*args)
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value:
+            return caseassignment.get_label(self.syndrome_id, value)
+
+
+class DemogSystemId(DemogField):
+    name = 'case_id'
+    label = 'System ID'
+    render = 'value'
+    #render_search = 'textinput'
+    show_person = False
+    field_case = field_form = 'case.case_row.case_id'
+    #field_search = 'search.case_id'
+    field_result = 'case_id'
+    section = 'id'
+    entity = 'case'
+
+
+class DemogSurname(DemogField):
+    name = 'surname'
+    label = 'Surname'
+    render = 'textinput'
+#    hideable = False
+    field_case = field_form = 'case.person.surname'
+    field_search = field_person = 'search.person.surname'
+    field_result = 'surname'
+    section = 'name'
+    entity = 'person'
+
+    def summary(self, ns):
+        return DemogField.outtrans(self, ns) or None
+
+
+class DemogGivenNames(DemogField):
+    name = 'given_names'
+    label = 'Given names'
+    render = 'textinput'
+    field_case = field_form = 'case.person.given_names'
+    field_search = field_person = 'search.person.given_names'
+    field_result = 'given_names'
+    section = 'name'
+    entity = 'person'
+
+    def summary(self, ns):
+        return DemogField.outtrans(self, ns) or None
+
+
+class DemogDOB(DatetimeBase):
+    name = 'DOB'
+    label = 'Date of birth/Age'
+    render_case = 'case_dob'
+    field_case = 'case.person.DOB_edit'
+    field_form = 'case.person.DOB'
+    render_search = render_person = 'datetimeinput'
+    field_search = field_person = 'search.person.DOB_edit'
+    field_result = 'DOB'
+    entity = 'person'
+
+    def age_if_dob(self, ns):
+        return agelib.age_if_dob(_getpathattr(ns, self.field or self.name))
+
+    def outtrans(self, ns):
+        attr = self.field or self.name
+        dob = _getpathattr(ns, attr)
+        prec = _getpathattr(ns, attr + '_prec')
+        return agelib.dobage_str(dob, prec)
+
+    def summary(self, ns):
+        return self.outtrans(ns)
+
+
+class DemogSex(SelDemogField):
+    name = 'sex'
+    label = 'Sex'
+    render = 'select'
+    field_case = field_form = 'case.person.sex'
+    field_search = field_person = 'search.person.sex'
+    field_result = 'sex'
+    entity = 'person'
+
+    def optionexpr(self):
+        return person.sexes
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if value and value != 'U':
+            return person.expandsex(value)
+
+    def summary(self, ns):
+        return self.outtrans(ns)
+
+
+class DemogHomePhone(DemogField):
+    name = 'home_phone'
+    label = 'Home phone'
+    render = 'textinput'
+    show_form = False
+    show_result = False
+    field_case = field_form = 'case.person.home_phone'
+    field_search = field_person = 'search.person.home_phone'
+    field_result = 'home_phone'
+    section = 'phone'
+    entity = 'person'
+
+
+class DemogMobilePhone(DemogField):
+    name = 'mobile_phone'
+    label = 'Mobile phone'
+    render = 'textinput'
+    show_form = False
+    show_result = False
+    field_case = field_form = 'case.person.mobile_phone'
+    field_search = field_person = 'search.person.mobile_phone'
+    field_result = 'mobile_phone'
+    section = 'phone'
+    entity = 'person'
+
+
+class DemogFaxPhone(DemogField):
+    name = 'fax_phone'
+    label = 'Fax'
+    render = 'textinput'
+    show_form = False
+    show_result = False
+    field_case = field_form = 'case.person.fax_phone'
+    field_search = field_person = 'search.person.fax_phone'
+    field_result = 'fax_phone'
+    section = 'phone'
+    entity = 'person'
+
+
+class DemogEMail(DemogField):
+    name = 'e_mail'
+    label = 'e-mail'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.e_mail'
+    field_search = field_person = 'search.person.e_mail'
+    field_result = 'e_mail'
+    section = 'phone'
+    entity = 'person'
+
+
+class DemogStreetAddress(DemogField):
+    name = 'street_address'
+    label = 'Street address'
+    render = 'textinput'
+    show_form = False
+    show_result = False
+    field_case = field_form = 'case.person.street_address'
+    field_search = field_person = 'search.person.street_address'
+    field_result = 'street_address'
+    section = 'address'
+    entity = 'person'
+
+
+class DemogLocality(DemogField):
+    name = 'locality'
+    label = 'Locality/Suburb'
+    render = 'textinput'
+    field_case = field_form = 'case.person.locality'
+    field_search = field_person = 'search.person.locality'
+    field_result = 'locality'
+    section = 'address'
+    entity = 'person'
+
+
+class DemogState(DemogStateBase):
+    name = 'state'
+    label = 'State'
+    render = 'select'
+    show_form = False
+    field_case = field_form = 'case.person.state'
+    field_search = field_person = 'search.person.state'
+    field_result = 'state'
+    section = 'address'
+    entity = 'person'
+
+
+class DemogPostcode(DemogField):
+    name = 'postcode'
+    label = 'Postcode'
+    render = 'textinput'
+    show_result = False
+    field_case = field_form = 'case.person.postcode'
+    field_search = field_person = 'search.person.postcode'
+    field_result = 'postcode'
+    section = 'address'
+    entity = 'person'
+
+
+class DemogCountry(DemogCountryBase):
+    name = 'country'
+    label = 'Country'
+    field_case = field_form = 'case.person.country'
+    field_search = field_person = 'search.person.country'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_result = 'country'
+    section = 'address'
+    entity = 'person'
+
+
+class AltDemogStreetAddress(DemogField):
+    name = 'alt_street_address'
+    label = 'Alternate Street address'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.alt_street_address'
+    field_search = field_person = 'search.person.alt_street_address'
+    field_result = 'alt_street_address'
+    section = 'alt_address'
+    entity = 'person'
+
+
+class AltDemogLocality(DemogField):
+    name = 'alt_locality'
+    label = 'Alternate Locality/Suburb'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.alt_locality'
+    field_search = field_person = 'search.person.alt_locality'
+    field_result = 'alt_locality'
+    section = 'alt_address'
+    entity = 'person'
+
+
+class AltDemogState(DemogStateBase):
+    name = 'alt_state'
+    label = 'Alternate State'
+    render = 'select'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.alt_state'
+    field_search = field_person = 'search.person.alt_state'
+    field_result = 'alt_state'
+    section = 'alt_address'
+    entity = 'person'
+
+
+class AltDemogPostcode(DemogField):
+    name = 'alt_postcode'
+    label = 'Alternate Postcode'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.alt_postcode'
+    field_search = field_person = 'search.person.alt_postcode'
+    field_result = 'alt_postcode'
+    section = 'alt_address'
+    entity = 'person'
+
+
+class AltDemogCountry(DemogCountryBase):
+    name = 'alt_country'
+    label = 'Alternate Country'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.alt_country'
+    field_search = field_person = 'search.person.alt_country'
+    field_result = 'alt_country'
+    section = 'alt_address'
+    entity = 'person'
+
+
+class WorkDemogStreetAddress(DemogField):
+    name = 'work_street_address'
+    label = 'Work/School Street address'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.work_street_address'
+    field_search = field_person = 'search.person.work_street_address'
+    field_result = 'work_street_address'
+    section = 'occupation'
+    entity = 'person'
+
+
+class WorkDemogLocality(DemogField):
+    name = 'work_locality'
+    label = 'Work/School Locality/Suburb'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.work_locality'
+    field_search = field_person = 'search.person.work_locality'
+    field_result = 'work_locality'
+    section = 'occupation'
+    entity = 'person'
+
+
+class WorkDemogState(DemogStateBase):
+    name = 'work_state'
+    label = 'Work/School State'
+    render = 'select'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.work_state'
+    field_search = field_person = 'search.person.work_state'
+    field_result = 'work_state'
+    section = 'occupation'
+    entity = 'person'
+
+
+class WorkDemogPostcode(DemogField):
+    name = 'work_postcode'
+    label = 'Work/School Postcode'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.work_postcode'
+    field_search = field_person = 'search.person.work_postcode'
+    field_result = 'work_postcode'
+    section = 'occupation'
+    entity = 'person'
+
+
+class WorkDemogCountry(DemogCountryBase):
+    name = 'work_country'
+    label = 'Work/School Country'
+    field_case = field_form = 'case.person.work_country'
+    field_search = field_person = 'search.person.work_country'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_result = 'work_country'
+    section = 'occupation'
+    entity = 'person'
+
+
+class WorkDemogPhone(DemogField):
+    name = 'work_phone'
+    label = 'Work/School phone'
+    render = 'textinput'
+    show_form = False
+    show_result = False
+    field_case = field_form = 'case.person.work_phone'
+    field_search = field_person = 'search.person.work_phone'
+    field_result = 'work_phone'
+    section = 'occupation'
+    entity = 'person'
+
+
+class DemogOccupation(DemogField):
+    name = 'occupation'
+    label = 'Occupation'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.occupation'
+    field_search = field_person = 'search.person.occupation'
+    field_result = 'occupation'
+    section = 'occupation'
+    entity = 'person'
+
+
+class DemogPassportNumber(DemogField):
+    name = 'passport_number'
+    label = 'Passport number'
+    render = 'textinput'
+    show_result = False
+    field_case = field_form = 'case.person.passport_number'
+    field_search = field_person = 'search.person.passport_number'
+    field_result = 'passport_number'
+    section = 'passport'
+    entity = 'person'
+
+
+class DemogPassportCountry(DemogCountryBase):
+    name = 'passport_country'
+    label = 'Passport country/Nationality'
+    field_case = field_form = 'case.person.passport_country'
+    field_search = field_person = 'search.person.passport_country'
+    field_result = 'passport_country'
+    section = 'passport'
+    entity = 'person'
+
+
+class DemogPassportNumber2(DemogField):
+    name = 'passport_number_2'
+    label = 'Second passport number'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.passport_number_2'
+    field_search = field_person = 'search.person.passport_number_2'
+    field_result = 'passport_number_2'
+    section = 'passport'
+    entity = 'person'
+
+
+class DemogPassportCountry2(DemogCountryBase):
+    name = 'passport_country_2'
+    label = 'Second passport country/Nationality'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.person.passport_country_2'
+    field_search = field_person = 'search.person.passport_country_2'
+    field_result = 'passport_country_2'
+    section = 'passport'
+    entity = 'person'
+
+
+class DemogNotes(DemogField):
+    name = 'notes'
+    label = 'Other Information'
+    render = 'textarea'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.case_row.notes'
+#    field_search = field_person = None
+    field_result = 'notes'
+    section = 'notes'
+    entity = 'case'
+
+
+class DemogNotificationDate(DatetimeBase):
+    name = 'notification_datetime'
+    label = 'Notification Date'
+    show_person = False
+    show_result = False
+    field_case = field_form = 'case.case_row.notification_datetime'
+    field_result = 'notification_datetime'
+    section = 'notification'
+    entity = 'case'
+
+
+class DemogOnsetDate(DatetimeBase):
+    name = 'onset_datetime'
+    label = 'Onset Date'
+    render = 'datetimeinput'
+    show_search = show_person = False
+    field_case = field_form = 'case.case_row.onset_datetime'
+    field_result = 'onset_datetime'
+    section = 'notification'
+    entity = 'case'
+
+
+class DemogNotifierName(DemogField):
+    name = 'notifier_name'
+    label = 'Notifier name'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.case_row.notifier_name'
+    field_search = field_person = 'search.notifier_name'
+    field_result = 'notifier_name'
+    section = 'notification'
+    entity = 'case'
+
+
+class DemogNotifierContact(DemogField):
+    name = 'notifier_contact'
+    label = 'Notifier contact details'
+    render = 'textarea'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_case = field_form = 'case.case_row.notifier_contact'
+#    field_search = field_person = 'search..notifier_contact'
+    field_result = 'notifier_contact'
+    section = 'notification'
+    entity = 'case'
+
+
+class DemogTags(DemogField):
+    name = 'tags'
+    label = 'Tags'
+    render = 'tags'
+    show_case = show_search = show_result = True
+    section = None
+    entity = 'case'
+    field_search = field_person = 'search.tags'
+    field_case = 'case.tags.cur'
+    field_result = 'tags'
+
+    def optionexpr(self):
+        return [(ti.tag, ti.tag) for ti in casetags.tags()]
+
+
+class DemogDeleted(DemogField):
+    name = 'deleted'
+    label = 'Deleted'
+    render = render_person = 'short_radio'
+    show_person = False
+    field_search = field_person = 'search.deleted'
+    field_result = 'deleted'
+    entity = 'case'
+
+    def optionexpr(self):
+        return [
+            ('n', 'No'),
+            ('y', 'Yes'),
+            ('', 'Both'),
+        ]
+
+    def outtrans(self, ns):
+        value = DemogField.outtrans(self, ns)
+        if self.context in ('search', 'person'):
+            if value == 'y':
+                return 'DELETED only'
+            elif value == '':
+                return 'Include DELETED'
+        elif value:
+            return 'DELETED'
+
+    def summary(self, ns):
+        return self.outtrans(ns)
+
+
+class DemogDeleteReason(DemogField):
+    name = 'delete_reason'
+    label = 'Deletion reason'
+    render = 'textinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_result = 'delete_reason'
+    entity = 'case'
+
+
+class DemogDeleteTimestamp(DatetimeBase):
+    name = 'delete_timestamp'
+    label = 'Deletion date'
+    render = 'datetimeinput'
+    show_case = show_form = show_search = show_person = show_result = False
+    field_result = 'delete_timestamp'
+    entity = 'case'
+
+
+class DemogDataSrc(DemogField):
+    name = 'data_src'
+    label = 'Data source'
+    render = 'textinput'
+    #field_case = field_form = 'case.person.data_src'
+    field_search = field_person = 'search.person.data_src'
+    #field_result = 'data_src'
+    show_case = show_form = show_search = show_person = show_result = False
+    entity = 'person'
+
+
+def rows_and_cols(fields, row_width=2):
+    rows = []
+    row = []
+    section = None
+    for field in fields:
+        if len(row) >= row_width or (field.section != section and row):
+            rows.append(row)
+            row = []
+        section = field.section
+        row.append(field)
+    if row:
+        rows.append(row)
+    return rows
+
+
+class DemogFieldsBase(list):
+    group_defs = [
+        (None, None),
+        ('Address', 'address'),
+        ('Alt Address', 'alt_address'),
+        ('Work/School', 'occupation'),
+        ('Passport', 'passport'),
+        ('Notification', 'notification'),
+        ('Notes', 'notes'),
+    ]        
+    groups = None
+    group_by_name = None
+
+    def __init__(self):
+        self.fields_by_name = {}
+
+    def add_field(self, field):
+        self.append(field)
+        self.fields_by_name[field.name] = field
+
+    def field_by_name(self, name):
+        return self.fields_by_name[name]
+
+    def rows_and_cols(self, row_width=2):
+        return rows_and_cols(self, row_width)
+
+    def summary(self, ns):
+        summary = []
+        for field in self:
+            field_summary = field.summary(ns)
+            if field_summary:
+                summary.append(field_summary)
+        return ', '.join(summary)
+
+    def grouped(self):
+        if self.groups is None:
+            self.groups = []
+            self.group_by_name = {}
+            if len(self) > config.tabbed_demogfields_threshold:
+                groups = []
+                group_by_section = {}
+                for group_def in self.group_defs:
+                    group = GroupDemogFields(*group_def)
+                    for section in group.sections:
+                        group_by_section[section] = group
+                    groups.append(group)
+                anon_group = groups[0]
+                for field in self:
+                    group = group_by_section.get(field.section, anon_group)
+                    group.add_field(field)
+                for group in groups:
+                    if group:
+                        self.groups.append(group)
+                        self.group_by_name[group.name] = group
+            if len(self.groups) < 3:
+                group = GroupDemogFields(None, None)
+                for field in self:
+                    group.add_field(field)
+                self.groups = [group]
+                self.group_by_name = {None: group}
+        return self.groups
+
+    def group(self, name):
+        if self.group_by_name is None:
+            self.grouped()
+        try:
+            return self.group_by_name[name]
+        except KeyError:
+            # This generally shouldn't happen, but admin changes to demogfields
+            # and other obscure stuff can result in an invalid group request,
+            # so don't throw an ugly error.
+            return GroupDemogFields('', '')
+
+    def tabs(self, initial=None):
+        from casemgr.tabs import Tabs
+        tabs = Tabs(initial)
+        for group in self.grouped():
+            if group.label:
+                tabs.add(group.name, group.label)
+        tabs.done()
+        return tabs
+
+
+class GroupDemogFields(DemogFieldsBase):
+    def __init__(self, label, *sections):
+        DemogFieldsBase.__init__(self)
+        self.name = sections[0]
+        self.label = label
+        self.sections = sections
+
+
+class ContextDemogFields(DemogFieldsBase):
+    def __init__(self, demog_fields, context, *opts):
+        DemogFieldsBase.__init__(self)
+        for demog_field in demog_fields:
+            if demog_field.show(context):
+                self.add_field(demog_field.context_field(context, *opts))
+
+
+class ReportContextDemogFields(DemogFieldsBase):
+    """
+    The "report" context includes all "case" context fields, plus fields that
+    are hard-coded in the UI.
+    """
+    forced = (
+        'deleted', 'delete_reason', 'delete_timestamp', 'data_src',
+    )
+    def __init__(self, demog_fields):
+        DemogFieldsBase.__init__(self)
+        for demog_field in demog_fields:
+            if (demog_field.name in self.forced or demog_field.show('case')):
+                self.add_field(demog_field)
+
+
+class ReorderedDemogFields(DemogFieldsBase):
+    def __init__(self, demog_fields, order):
+        DemogFieldsBase.__init__(self)
+        field_map = {}
+        for field in demog_fields:
+            field_map[field.name] = field
+        for name in order:
+            field = field_map.pop(name, None)
+            if field:
+                self.add_field(field)
+        for field in demog_fields:
+            if field.name in field_map:
+                self.add_field(field)
+
+
+class DemogFields(DemogFieldsBase):
+    def __init__(self, db, syndrome_id, save_initial=False):
+        DemogFieldsBase.__init__(self)
+        self.syndrome_id = syndrome_id
+        self.context_cache = {}
+        common_rows_by_name, rows_by_name = self.rows_dict(db)
+        for demog_field_cls in demog_classes:
+            row = rows_by_name.get(demog_field_cls.name)
+            common_row = common_rows_by_name.get(demog_field_cls.name)
+            field = demog_field_cls(syndrome_id, row or common_row, 
+                                    save_initial)
+            self.add_field(field)
+
+    def rows_dict(self, db, for_update=False):
+        rows_by_name = {}
+        common_rows_by_name = {}
+        query = db.query(DEMOG_TABLE, for_update=for_update)
+        if self.syndrome_id is None:
+            query.where('syndrome_id is null')
+        else:
+            query.where('(syndrome_id is null OR syndrome_id = %s)', 
+                        self.syndrome_id)
+        for row in query.fetchall():
+            if row.syndrome_id == self.syndrome_id:
+                rows_by_name[row.name] = row
+            elif row.syndrome_id is None:
+                common_rows_by_name[row.name] = row
+        return common_rows_by_name, rows_by_name
+
+    def context_fields(self, context, disabled=False):
+        cache_key = context, disabled
+        try:
+            context_fields = self.context_cache[cache_key]
+        except KeyError:
+            if context == 'report':
+                context_fields = ReportContextDemogFields(self)
+            else:
+                context_fields = ContextDemogFields(self, context, disabled)
+            self.context_cache[cache_key] = context_fields
+        return context_fields
+
+    def reordered_context_fields(self, order, context, *opts):
+        fields = self.context_fields(context, *opts)
+        if not order:
+            return fields
+        def basename(name):
+            return name.split('.')[-1]
+        # This is abusing the context_cache to cache reordered fields
+        order = tuple(map(basename, order))
+        key = (context, False) + order
+        try:
+            context_fields = self.context_cache[key]
+        except KeyError:
+            context_fields = ReorderedDemogFields(fields, order)
+            self.context_cache[key] = context_fields
+        return context_fields
+
+    ### Methods from this point on are only used when editing fields
+    def has_changed(self):
+        for demog_field in self:
+            if demog_field.has_changed():
+                return True
+        return False
+
+    def update(self, db):
+        common_rows_by_name, rows_by_name = self.rows_dict(db, for_update=True)
+        for demog_field in self:
+            try:
+                row = rows_by_name[demog_field.name]
+            except KeyError:
+                row = db.new_row(DEMOG_TABLE)
+                row.syndrome_id = self.syndrome_id
+                row.name = demog_field.name
+            cls = demog_field.__class__
+            common_row = common_rows_by_name.get(demog_field.name)
+            demog_field.update(row, cls(None, common_row))
+
+
+class DemogFieldCache(object):
+    subscribed = False
+
+    def __init__(self):
+        self.fields_by_syndrome = {}
+
+    def notification(self, *args):
+        for id in args:
+            if id == 'None':
+                self.flush()
+            else:
+                try:
+                    del self.fields_by_syndrome[int(id)]
+                except KeyError:
+                    pass
+
+    def flush(self):
+        self.fields_by_syndrome.clear()
+
+    def get_demog_fields(self, db, syndrome_id):
+        if not self.subscribed:
+            from globals import notify
+            self.subscribed = True
+            if not notify.subscribe('demogfields', self.notification):
+                # Notification not available - use time based refresh.
+                pass
+        now = time.time()
+        try:
+            demog_fields = self.fields_by_syndrome[syndrome_id]
+        except KeyError:
+            demog_fields = None
+        else:
+            if demog_fields.fetch_time + TTL < now:
+                demog_fields = None
+        if demog_fields is None:
+            demog_fields = DemogFields(db, syndrome_id)
+            demog_fields.fetch_time = now
+            self.fields_by_syndrome[syndrome_id] = demog_fields
+        return demog_fields
+
+
+demog_field_cache = DemogFieldCache()
+get_demog_fields = demog_field_cache.get_demog_fields
+flush = demog_field_cache.flush
diff --git a/casemgr/exportselect.py b/casemgr/exportselect.py
new file mode 100644
index 0000000..cb87a26
--- /dev/null
+++ b/casemgr/exportselect.py
@@ -0,0 +1,93 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+
+from casemgr import globals, syndrome, dataexport
+
+import config
+
+class ExportSelect:
+    def __init__(self, syndrome_id):
+        self.syndrome_id = syndrome_id
+        self.export_scheme = 'classic'
+        self.deleted = 'n'
+        self.strip_newlines = False
+        self.exporter = None
+        self.ready = False
+        self.saved_args = None
+        self.clear_forms()
+
+    def show_schemes(self):
+        return bool(self.syndrome_id)
+
+    def scheme_options(self):
+        return [
+            ('classic', 'By case, include form name in column labels'),
+            ('doha', 'By case, short column labels'),
+            ('form', 'By form'),
+            ('contacts', 'Case %ss' % config.contact_label),
+        ]
+
+    def multi_form_sel(self):
+        return self.export_scheme != 'form'
+
+    def clear_forms(self):
+        self.include_forms = []
+
+    def select_all_forms(self):
+        if self.exporter is None:
+            self.include_forms = []
+        else:
+            self.include_forms = [form.label for form in self.exporter.forms]
+
+    def refresh(self, credentials):
+        args = [self.syndrome_id]
+        if self.export_scheme == 'contacts':
+            cls = dataexport.ContactExporter
+        else:
+            cls = dataexport.CaseExporter
+        kwargs = {}
+        kwargs['format'] = self.export_scheme
+        kwargs['deleted'] = self.deleted
+        kwargs['strip_newlines'] = (str(self.strip_newlines) == 'True')
+#        print >> sys.stderr, 'ExportSelect: %s saved %r now %r' %\
+#            (self.exporter.__class__, self.saved_args, (args, kwargs))
+        if (self.exporter is None or not isinstance(self.exporter, cls)
+                or self.saved_args != (args, kwargs)):
+            self.exporter = cls(credentials, *args, **kwargs)
+            self.saved_args = args, kwargs
+            if self.multi_form_sel():
+                if not isinstance(self.include_forms, list):
+                    self.include_forms = [self.include_forms]
+            else:
+                if isinstance(self.include_forms, list):
+                    if self.include_forms:
+                        self.include_forms = self.include_forms[0]
+                    else:
+                        self.include_forms = None
+            return True
+        return False
+
+    def filename(self):
+        return self.exporter.filename()
+
+    def row_gen(self):
+        include_forms = self.include_forms
+        if not isinstance(include_forms, list):
+            include_forms = [include_forms]
+        return self.exporter.row_gen(include_forms)
diff --git a/casemgr/form_summary.py b/casemgr/form_summary.py
new file mode 100644
index 0000000..8a19836
--- /dev/null
+++ b/casemgr/form_summary.py
@@ -0,0 +1,429 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard libraries
+from time import time
+
+# Application modules
+from cocklebur import form_ui, datetime
+from casemgr import globals, cached
+import config
+
+# NOTE - due to a historical mistake the form abstractions transpose the common
+# meanings of "name" and "label": the form "name" is a short description
+# intended user consumption, and "label" is the system name from which table
+# names and file names are derived. Sorry - AM
+
+def instance_summary(form, instance_row):
+    return '; '.join(form.collect_summary(instance_row))
+
+
+class EditForm(object):
+    """
+    Represents an active edit of a case form (corresponds to a row in the
+    summary and form instance tables).
+    """
+    def __init__(self, case_id, form, summary=None, 
+                 data_src=None, force_multiple=False):
+        self.case_id = case_id
+        self.name = form.name
+        self.label = form.label
+        self.version = form.cur_version
+        self.allow_multiple = form.allow_multiple
+        self.data_src = data_src
+        if force_multiple:
+            self.allow_multiple = True
+        if summary is None:
+            self.summary_id = None
+            self.deleted = False
+            self.delete_reason = None
+            self.delete_timestamp = None
+        else:
+            self.summary_id = summary.summary_id
+            self.data_src = summary.data_src
+            self.deleted = summary.deleted
+            self.delete_reason = summary.delete_reason
+            self.delete_timestamp = summary.delete_timestamp
+        form = self.get_form_ui()
+        self.instance_row = form_ui.load_form_data(globals.db, form, 
+                                                   self.summary_id)
+
+    def get_form_data(self):
+        return self.instance_row
+
+    def viewonly(self):
+        return bool(self.deleted or self.data_src)
+
+    def foreign_src(self):
+        return bool(not self.deleted and self.data_src)
+
+    def get_form_ui(self):
+        try:
+            return globals.formlib.load(self.label, self.version)
+        except form_ui.FormError:
+            import traceback
+            traceback.print_exc()
+            # This is a user app, so we hide the scary error
+            raise form_ui.FormError('the %r form type is unavailable due to '
+                                    'problems with it\'s structure' % 
+                                        self.label)
+
+    def validate(self):
+        return self.get_form_ui().validate(self.instance_row)
+
+    def db_desc(self):
+        return self.instance_row.db_desc()
+
+    def has_changed(self):
+        return self.instance_row.db_has_changed()
+
+    def take_ownership(self):
+        assert self.data_src
+        self.data_src = None
+
+    def reload_instance(self, msg):
+        """
+        Our instance row cannot be saved (form upgrade or a conflicting
+        update of a singleton form) - reload and merge our changes
+        into the loaded row.
+        """
+        instance_row = form_ui.load_form_data(globals.db, self.get_form_ui(),
+                                              self.summary_id)
+        instance_row.db_merge(self.instance_row)
+        self.instance_row = instance_row
+        raise globals.ReviewForm(msg)
+
+    def update(self):
+        if config.form_rollforward:
+            # Detect if the form definition has been upgraded while we were
+            # editing. If so, update the dbobj to refer to the correct table,
+            # and force the user to review the form before saving again.
+            query = globals.db.query('forms', for_update=True)
+            query.where('label = %s', self.label)
+            deployed_version = query.aggregate('cur_version')
+            if deployed_version != self.version:
+                self.version = deployed_version
+                self.reload_instance(
+                    'The definition of this form was changed while you were '
+                    'editing it. Please review all fields carefully before '
+                    'saving it.') 
+        summary_row = None
+        if self.summary_id is None and not self.allow_multiple:
+            # If form is a singleton, ensure nobody else created one while we
+            # were editing - if they did, load their form, merge our fields
+            # into it and then force the user to review the result before
+            # saving again.
+            globals.db.lock_table('case_form_summary', 'EXCLUSIVE')
+            query = globals.db.query('case_form_summary', for_update=True)
+            query.where('form_label = %s', self.label)
+            query.where('case_id = %s', self.case_id)
+            query.where('NOT deleted')
+            summary_row = query.fetchone()
+            if summary_row is not None:
+                self.summary_id = summary_row.summary_id
+                self.reload_instance(
+                    'Another user has already created this form. Your data '
+                    'has been merged into their form. Please review the '
+                    'changes carefully, and then save.')
+        else:
+            query = globals.db.query('case_form_summary', for_update=True)
+            query.where('summary_id = %s', self.summary_id)
+            summary_row = query.fetchone()
+        if summary_row is None:
+            summary_row = globals.db.new_row('case_form_summary')
+            self.summary_id = summary_row.db_nextval('summary_id')
+            summary_row.form_label = self.label
+            summary_row.summary_id = self.summary_id
+            summary_row.case_id = self.case_id
+            self.instance_row.summary_id = self.summary_id
+        summary_row.form_version = self.version
+        summary_row.data_src = self.data_src
+        if self.instance_row.form_date:
+            summary_row.form_date = self.instance_row.form_date
+        form = self.get_form_ui()
+        summary_row.summary = instance_summary(form, self.instance_row)
+        summary_row.db_update()
+        if not self.instance_row.form_date:
+            self.instance_row.form_date = summary_row.form_date
+        was_new = self.instance_row.is_new()
+        self.instance_row.db_update()
+        task_info = dict(form_name=self.label, 
+                         summary_id=self.summary_id, 
+                         is_new=was_new)
+        return task_info
+
+    def set_deleted(self, delete, reason=None):
+        if delete:
+            timestamp = datetime.now().mx()
+        else:
+            timestamp = None
+            reason = None
+        query = globals.db.query('case_form_summary')
+        query.where('summary_id = %s', self.summary_id)
+        query.update('deleted=%s, delete_reason=%s, delete_timestamp=%s',
+                     delete, reason, timestamp)
+        globals.db.commit()
+        self.deleted = delete
+
+    def task_info(self):
+        return dict(form_name=self.label, 
+                    summary_id=self.summary_id,
+                    is_new=self.summary_id is None)
+
+    def abort(self):
+        task_info = self.task_info()
+        self.instance_row.db_revert()
+        return task_info
+
+
+def _getform(label, syndrome_id=None):
+    query = globals.db.query('forms')
+    query.where('label = %s', label)
+    if syndrome_id is not None:
+        subq = query.in_select('label', 'syndrome_forms', 
+                               columns=('form_label',))
+        subq.where('syndrome_id = %s', syndrome_id)
+    form = query.fetchone()
+    if form is None:
+        raise form_ui.FormError('Nonexistent form: %r' % label)
+    return form
+
+
+def _getsummary(summary_id):
+    query = globals.db.query('case_form_summary')
+    query.where('summary_id = %s', summary_id)
+    return query.fetchone()
+
+
+def new_form(syndrome_id, case_id, label):
+    # New form - are we allowed to create it?
+    form = _getform(label, syndrome_id)
+    if not form.allow_multiple:
+        query = globals.db.query('case_form_summary')
+        query.where('case_id = %s', case_id)
+        query.where('form_label = %s', form.label)
+        query.where('NOT deleted')
+        if query.aggregate('count(summary_id)') > 0:
+            raise form_ui.FormError('The %r form has already been'
+                                    ' completed' % form.name)
+    return EditForm(case_id, form)
+
+
+def edit_form(summary_id):
+    summary = _getsummary(summary_id)
+    if summary is None:
+        raise form_ui.FormError('Form not found')
+    form = _getform(summary.form_label)
+    return EditForm(summary.case_id, form, summary)
+
+
+class FormDataImp:
+
+    def __init__(self, syndrome_id, label, data_src):
+        assert data_src
+        self.form = _getform(label, syndrome_id)
+        self.data_src = data_src
+
+    def edit(self, case_id):
+        # Import deliberately ignores allow_multiple (via force_multiple) at
+        # this time as it is too difficult to handle the resulting RequireMerge
+        # exceptions.
+        query = globals.db.query('case_form_summary')
+        query.where('case_id = %s', case_id)
+        query.where('form_label = %s', self.form.label)
+        query.where('data_src = %s', self.data_src)
+        query.where('NOT deleted')
+        summary = query.fetchone()
+        if summary is None:
+            edit_form = EditForm(case_id, self.form, None, 
+                                 data_src=self.data_src, 
+                                 force_multiple=True)
+        else:
+            edit_form = EditForm(summary.case_id, self.form, summary, 
+                                 force_multiple=True)
+        return edit_form, edit_form.get_form_data()
+
+
+class FormSummary:
+    """
+    A summary of a single form
+    """
+
+    def __init__(self, summary_row):
+        self.summary_id = summary_row.summary_id
+        self.form_label = summary_row.form_label
+        self.form_version = summary_row.form_version
+        self.form_date = summary_row.form_date
+        self.data_src = summary_row.data_src
+        self.summary = summary_row.summary
+        self.deleted = summary_row.deleted
+        self.delete_reason = summary_row.delete_reason
+        self.delete_timestamp = summary_row.delete_timestamp
+
+    def update_summary(self, form, instance_row):
+        self.summary = instance_summary(form, instance_row)
+
+
+class FormSummaries:
+    """
+    Represents the collection of summaries of a given syndrome form
+    """
+    def __init__(self, form_row, case_id):
+        self.label = form_row.label
+        self.version = form_row.cur_version
+        self.name = form_row.name
+        self.allow_multiple = form_row.allow_multiple
+        self.allow_new = True
+        self.case_id = case_id
+        self.summaries = []
+        self.summaries_by_id = {}
+        self.summaries_by_src = {}
+
+    def add_summary_from_row(self, summary_row):
+        summary = FormSummary(summary_row)
+        self.summaries.append(summary)
+        self.summaries_by_id[summary.summary_id] = summary
+        if not self.allow_multiple and not summary.deleted:
+            self.allow_new = False
+        if summary.data_src:
+            self.summaries_by_src[summary.data_src] = summary
+
+    def load_summaries(self):
+        by_version = {}
+        for s in self.summaries:
+            by_version.setdefault(s.form_version, []).append(s.summary_id)
+        for version, ids in by_version.items():
+            try:
+                form_def = globals.formlib.load(self.label, version)
+            except form_ui.FormError:
+                continue        # Nothing else we can do for now
+            query = globals.db.query(form_def.table)
+            query.where_in('summary_id', ids)
+            for instance_row in query.fetchall():
+                try:
+                    summary = self.summaries_by_id[instance_row.summary_id]
+                except KeyError:
+                    pass
+                else:
+                    summary.update_summary(form_def, instance_row)
+
+    def allow_new_form(self):
+        """
+        Can the current form have new instances?
+
+        This test is cheap and potentially inaccurate - it is called
+        repeatedly while rendering the UI. A more comprehensive
+        test is done when the user actually attempts to create a
+        new form instance, and again when a form is saved.
+        """
+        return self.allow_new
+
+
+# We'd rather use a generator function or a real iterator, but, at the time,
+# the Albatross <al-for> tag only supported indexable types.
+class FormEnumerator:
+
+    def __init__(self, forms):
+        self.forminfo = [(s.form_label, s.form_version, s.summary_id)
+                         for form in forms for s in form.summaries]
+
+    def __getitem__(self, i):
+        label, version, summary_id = self.forminfo[i]
+        form = globals.formlib.load(label, version)
+        instance_row = form_ui.load_form_data(globals.db, form, summary_id)
+        return form, instance_row
+
+            
+class FormsList(cached.Cached, list):
+    """
+    Represents the collection of forms applicable to a loaded case or contact.
+    """
+    time_to_live = 30
+
+    def __init__(self, syndrome_id, summary_order_by='form_date'):
+        self.syndrome_id = syndrome_id
+        self.summary_order_by = summary_order_by
+        self.by_name = None
+        self.case_id = None
+
+    def set_case(self, case_id):
+        if self.case_id != case_id:
+            self.case_id = case_id
+            self.cache_invalidate()
+
+    def getform(self, label):
+        if self.by_name is None:
+            self.load()
+        return self.by_name[label]
+
+    def load(self):
+        #import traceback
+        #traceback.print_stack()
+        del self[:]
+        self.by_name = {}
+        query = globals.db.query('forms', order_by='syndrome_forms_id')
+        query.join('JOIN syndrome_forms'
+                    ' ON syndrome_forms.form_label = forms.label')
+        query.where('syndrome_forms.syndrome_id = %s', self.syndrome_id)
+        for row in query.fetchall():
+            fs = FormSummaries(row, self.case_id) 
+            self.append(fs)
+            self.by_name[fs.label] = fs
+        query = globals.db.query('case_form_summary', 
+                                    order_by=('deleted', self.summary_order_by))
+        query.where('case_id = %s', self.case_id)
+        for row in query.fetchall():
+            try:
+                form_summaries = self.by_name[row.form_label]
+            except KeyError:
+                pass
+            else:
+                form_summaries.add_summary_from_row(row)
+        if not config.cache_form_summaries:
+            for form_summaries in self:
+                form_summaries.load_summaries()
+
+
+class FormsListMixin(object):
+
+    def __init__(self, syndrome_id):
+        self.forms = FormsList(syndrome_id)
+
+    def getform(self, form_label):
+        return self.forms.getform(form_label)
+
+    def enumerate_forms(self):
+        return FormEnumerator(self.forms)
+
+    def can_merge_forms(self):
+        self.forms.refresh()
+        for form in self.forms:
+            if len(form.summaries) > 1:
+                return True
+        return False
+
+    def form_data_src(self):
+        return self.edit_form.form_data_src()
+
+    def edit_form(self, summary_id):
+        self.forms.cache_invalidate()
+        return edit_form(summary_id)
+
+    def new_form(self, label):
+        self.forms.cache_invalidate()
+        return new_form(self.case_row.syndrome_id, self.case_row.case_id, label)
diff --git a/casemgr/formmerge.py b/casemgr/formmerge.py
new file mode 100644
index 0000000..c09608e
--- /dev/null
+++ b/casemgr/formmerge.py
@@ -0,0 +1,275 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, form_ui
+from casemgr import globals
+
+class FormMergeError(globals.Error): pass
+Error = FormMergeError
+class FormHasChanged(FormMergeError): pass
+
+class NullRow:
+    def __getattr__(self, name):
+        return None
+nullrow = NullRow()
+
+class MergeCol:
+    def __init__(self, merge, input, index):
+        self.merge = merge
+        self.name = input.column
+        self.label = input.label or input.column
+        self.error = None
+        value_a = input.get_value(merge.row_a)
+        value_b = input.get_value(merge.row_b)
+        self.conflict = (value_a and value_b and value_a != value_b)
+        if not value_a and not value_b:
+            self.source = 'd'
+        elif value_b and not value_a:
+            self.source = 'b'
+        elif self.conflict and isinstance(value_a, str) and value_a == 'None':
+            self.source = 'b'
+        else:
+            self.source = 'a'
+        self.source_field = 'formmerge.fields[%d].source' % index
+        self.set_butt = 'edit:%d' % index
+
+    def get_input(self):
+        return self.merge.get_input(self.name)
+
+    def outtrans(self, ns):
+        return self.get_input().outtrans(ns) or ''
+
+    def format(self):
+        return self.get_input().format()
+
+    def toggle_edit(self):
+        if self.source == 'e':
+            self.source = self.last_source
+        else:
+            input = self.get_input()
+            if not input.get_value(self.merge.form_data):
+                if self.source == 'a':
+                    input.nscopy(self.merge.row_a, self.merge.form_data)
+                elif self.source == 'b':
+                    input.nscopy(self.merge.row_b, self.merge.form_data)
+            self.last_source = self.source
+            self.source = 'e'
+
+    def validate(self):
+        if self.source == 'e':
+            # Only validate a field if there is manual input.
+            self.error = None
+            input = self.get_input()
+            try:
+                input.validate(self.merge.form_data)
+            except form_ui.ValidationError, e:
+                self.error = str(e)
+                return False
+        return True
+
+    def desc_edit(self):
+        input = self.get_input()
+        value_a = input.get_value(self.merge.row_a)
+        value_b = input.get_value(self.merge.row_b)
+        value_e = input.get_value(self.merge.form_data)
+        if self.source == 'e':
+            op = 'Edit'
+            hilite = value_e != value_a or value_e != value_b
+            ns = self.merge.form_data
+        elif self.source == 'a' and value_a != value_b:
+            op = 'A'
+            hilite = True
+            ns = self.merge.row_a
+        elif self.source == 'b' and value_a != value_b:
+            op = 'B'
+            hilite = True
+            ns = self.merge.row_b
+        elif self.source == 'd' and (value_a or value_b):
+            op = 'DELETE'
+            hilite = True
+            if value_a:
+                ns = self.merge.row_a
+            else:
+                ns = self.merge.row_b
+        else:
+            return None
+        return self.label, op, input.outtrans(ns), hilite
+
+    def apply(self, row_a, row_b):
+        input = self.get_input()
+        value_a = input.get_value(row_a)
+        value_b = input.get_value(row_b)
+        initial_a = input.get_value(self.merge.row_a)
+        initial_b = input.get_value(self.merge.row_b)
+        if initial_a != value_a or initial_b != value_b:
+            raise FormHasChanged
+        elif self.source == 'a':
+            src = self.merge.row_a
+        elif self.source == 'b':
+            src = self.merge.row_b
+        elif self.source == 'd':
+            src = nullrow
+        elif self.source == 'e':
+            src = self.merge.form_data
+        input.nscopy(src, row_a)
+        input.nscopy(src, row_b)
+        value = input.get_value(src)
+        return value_a != value, value_b != value
+
+
+class FormMerge:
+    def __init__(self, case_id, key_a, key_b):
+        self.case_id = case_id
+        self.key_a = key_a
+        self.key_b = key_b
+        self._load()
+        form = self.get_form_ui()
+        self.form_data = form_ui.load_form_data(globals.db, form, None)
+
+    def _load(self):
+        self.summary_a, self.summary_b = self._fetch_summary()
+        self.form_label = self.summary_a.form_label
+        self.form_version = self.summary_a.form_version
+        self.form_description = self._fetch_description()
+        self.row_a, self.row_b = self._fetch_rows()
+        self._init_fields()
+
+    def _fetch_summary(self, for_update=False):
+        summary_a = summary_b = None
+        query = globals.db.query('case_form_summary', for_update=for_update)
+        query.where('case_id = %s', self.case_id)
+        query.where_in('summary_id', (self.key_a, self.key_b))
+        for summary in query.fetchall():
+            if summary.summary_id == self.key_a:
+                summary_a = summary
+            elif summary.summary_id == self.key_b:
+                summary_b = summary
+        if summary_a is None or summary_b is None:
+            raise Error('Could not fetch form summary')
+        if summary_a.form_label != summary_b.form_label:
+            raise Error('Forms must be of the same type')
+        if summary_a.form_version != summary_b.form_version:
+            raise Error('Form definition version does not match!')
+        return summary_a, summary_b
+
+    def _fetch_description(self):
+        query = globals.db.query('forms')
+        query.where('label = %s', self.form_label)
+        row = query.fetchone()
+        return row.name
+
+    def _fetch_rows(self, for_update=False):
+        form = self.get_form_ui()
+        row_a = row_b = None
+        query = globals.db.query(form.table, for_update=for_update)
+        query.where_in('summary_id', (self.key_a, self.key_b))
+        for row in query.fetchall():
+            if row.summary_id == self.key_a:
+                row_a = row
+            elif row.summary_id == self.key_b:
+                row_b = row
+        if row_a is None or row_b is None:
+            raise Error('Could not fetch form data')
+        return row_a, row_b
+
+    def _add_field(self, input):
+        self.fields.append(MergeCol(self, input, len(self.fields)))
+
+    def _init_fields(self):
+        self.fields = []
+        form = self.get_form_ui()
+        self.form_date_input = None
+        try:
+            self.get_input('form_date')
+        except KeyError:
+            self.form_date_input = form_ui.DatetimeInput('form_date',
+                                                        label='Form date')
+            self._add_field(self.form_date_input)
+        for input in form.get_inputs():
+            self._add_field(input)
+
+    def get_form_ui(self):
+        return globals.formlib.load(self.form_label, self.form_version)
+
+    def get_input(self, name):
+        if name == 'form_date' and self.form_date_input is not None:
+            return self.form_date_input
+        return self.get_form_ui().columns.find_input(name)
+
+    def toggle_edit(self, index):
+        self.fields[index].toggle_edit()
+
+    def validate(self):
+        okay = True
+        for field in self.fields:
+            if not field.validate():
+                okay = False
+        return okay
+
+    def desc_edit(self):
+        edits = []
+        for mc in self.fields:
+            desc = mc.desc_edit()
+            if desc:
+                edits.append(desc)
+        return edits
+
+    def merge(self, credentials):
+        form = self.get_form_ui()
+        summary_a, summary_b = self._fetch_summary(for_update=True)
+        row_a, row_b = self._fetch_rows(for_update=True)
+        a_delta_count = b_delta_count = 0
+        for mc in self.fields:
+            try:
+                a_changed, b_changed = mc.apply(row_a, row_b)
+            except FormHasChanged:
+                row_a.db_revert()
+                row_b.db_revert()
+                self.row_a = row_a
+                self.row_b = row_b
+                self._init_fields()
+                raise
+            if a_changed:
+                a_delta_count += 1
+            if b_changed:
+                b_delta_count += 1
+        # Now decide which direction to merge
+        if b_delta_count > a_delta_count:
+            update_row, delete_row = row_a, row_b
+            update_summary, delete_summary = summary_a, summary_b
+        else:
+            update_row, delete_row = row_b, row_a
+            update_summary, delete_summary = summary_b, summary_a
+        assert update_row.summary_id == update_summary.summary_id
+        assert delete_row.summary_id == delete_summary.summary_id
+        # Describe and log the update
+        update_desc = update_row.db_desc()
+        delete_desc = delete_row.db_desc()
+        if not update_desc:
+            update_desc = 'no edits required'
+        if not delete_desc:
+            delete_desc = 'no edits required'
+        desc = 'Merge %s form, System ID %s, UPDATED %s, DELETED %s' %\
+                    (self.form_label, self.case_id, update_desc, delete_desc)
+        credentials.user_log(globals.db, desc, case_id=self.case_id)
+        update_summary.form_date = update_row.form_date
+        update_summary.summary = '; '.join(form.collect_summary(update_row))
+        update_row.db_update(refetch=False)
+        delete_row.db_delete()
+        update_summary.db_update(refetch=False)
+        delete_summary.db_delete()
+#        globals.db.rollback()   # While debugging
diff --git a/casemgr/formutils/__init__.py b/casemgr/formutils/__init__.py
new file mode 100644
index 0000000..4c64a84
--- /dev/null
+++ b/casemgr/formutils/__init__.py
@@ -0,0 +1,27 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+"""
+This module ties together handling of the "forms" and "case_form_summary"
+tables along with formlib operations. It's primarily used by the form
+editor.
+
+Functionality includes renaming and deleting forms, but this should be
+extended to include deployment and roll-forward.
+"""
diff --git a/casemgr/formutils/delete.py b/casemgr/formutils/delete.py
new file mode 100644
index 0000000..7b8f6a6
--- /dev/null
+++ b/casemgr/formutils/delete.py
@@ -0,0 +1,43 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj, form_ui
+
+from casemgr import globals
+
+from exclusiveop import exclusiveop
+
+def delete_form(forms_row, name):
+    try:
+        globals.formlib.delete(name)
+    except form_ui.NoFormError:
+        pass
+    for table_desc in globals.formlib.form_tables(globals.db, name):
+        globals.db.drop_table(table_desc.name)
+    # We haven't used an ON DELETE CASCADE here simply to make it harder for
+    # the admin to have have a catastrophic accident SQL typo...
+    curs = globals.db.cursor()
+    try:
+        dbobj.execute(curs, 'DELETE FROM case_form_summary '
+                            'WHERE form_label = %s', (name,))
+    finally:
+        curs.close()
+    forms_row.db_delete()
+    globals.db.save_describer()
+
+delete_form = exclusiveop(delete_form)
diff --git a/casemgr/formutils/deploy.py b/casemgr/formutils/deploy.py
new file mode 100644
index 0000000..c84f036
--- /dev/null
+++ b/casemgr/formutils/deploy.py
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+def make_form_table(db, form, table, **kwargs):
+    """
+    Given a form definition and table name, register and create the
+    associated instance table.
+    """
+    form.update_columns()
+    table_desc = db.new_table(table)
+    table_desc.column('summary_id', dbobj.ReferenceColumn,
+        references = 'case_form_summary', on_delete = 'CASCADE',
+        primary_key = True)
+    table_desc.column('form_date', dbobj.DatetimeColumn,
+        default = 'CURRENT_TIMESTAMP')
+    for column in form.columns:
+        try:
+            table_desc.get_column(column.name)
+        except KeyError:
+            table_desc.column(column.name, column.type, **column.kwargs)
+    db.make_table(table, **kwargs)
diff --git a/casemgr/formutils/exclusiveop.py b/casemgr/formutils/exclusiveop.py
new file mode 100644
index 0000000..34e6d65
--- /dev/null
+++ b/casemgr/formutils/exclusiveop.py
@@ -0,0 +1,40 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals
+
+def exclusiveop(fn):
+    def _exclusiveop(name, *args, **kwargs):
+        """
+        Obtain a row-locked copy of the current row, and pass it
+        to the supplied callback for processing. If the callback
+        returns, the row will be committed. If it raises an
+        exception, the transaction will be rolled back.
+        """
+        query = globals.db.query('forms', for_update=True)
+        query.where('label = %s', name)
+        try:
+            dbrow = query.fetchone()
+            if dbrow is None:
+                dbrow = globals.db.new_row('forms')
+            fn(dbrow, name, *args, **kwargs)
+        except:
+            globals.db.rollback()
+            raise
+        globals.db.commit()
+        return dbrow
+    return _exclusiveop
diff --git a/casemgr/formutils/rename.py b/casemgr/formutils/rename.py
new file mode 100644
index 0000000..667891b
--- /dev/null
+++ b/casemgr/formutils/rename.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+from casemgr import globals
+
+from exclusiveop import exclusiveop
+
+def rename_form(forms_row, old_name, new_name):
+    globals.formlib.rename(old_name, new_name)
+    forms_row.label = new_name
+    forms_row.db_update()
+    for table_desc in globals.formlib.form_tables(globals.db, old_name):
+        new_table = table_desc.name.replace(old_name, new_name)
+        globals.db.rename_table(table_desc.name, new_table)
+    globals.db.save_describer()
+
+rename_form = exclusiveop(rename_form)
diff --git a/casemgr/formutils/usedby.py b/casemgr/formutils/usedby.py
new file mode 100644
index 0000000..4a81d37
--- /dev/null
+++ b/casemgr/formutils/usedby.py
@@ -0,0 +1,48 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, cached
+
+class FormSyndromes(cached.Cached):
+    time_to_live = 10
+    def __init__(self, name):
+        self.name = name
+
+    def load(self):
+        query = globals.db.query('syndrome_types')
+        query.join('LEFT JOIN syndrome_forms USING (syndrome_id)')
+        query.where('form_label = %s', self.name)
+        self.form_syndromes = query.fetchcols('name')
+        self.form_syndromes.sort()
+
+class FormsSyndromes(dict):
+    def form_syndromes(self, name):
+        """
+        Return an ordered list of syndromes that use this form
+        """
+        if not name:
+            return []
+        name = name.lower()
+        try:
+            form_syndromes = self[name]
+        except KeyError:
+            form_syndromes = self[name] = FormSyndromes(name)
+        form_syndromes.refresh()
+        return form_syndromes.form_syndromes
+
+form_syndromes = FormsSyndromes().form_syndromes
diff --git a/casemgr/fuzzyperson.py b/casemgr/fuzzyperson.py
new file mode 100644
index 0000000..37b007c
--- /dev/null
+++ b/casemgr/fuzzyperson.py
@@ -0,0 +1,62 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import string
+import itertools
+
+from cocklebur import dbobj
+from casemgr.nickcache import get_nicks
+from casemgr.phonetic_encode import dmetaphone
+
+
+transmap = string.maketrans('-,', '  ')
+
+def encode_phones(*fields):
+    word_phones = []
+    for field in fields:
+        if field:
+            words = field.lower().translate(transmap).split()
+            wordnicks = get_nicks(words)
+            for nicks in wordnicks:
+                word_phones.append([dmetaphone(nick) for nick in nicks])
+    return word_phones
+
+
+def update(db, person_id, *names):
+    curs = db.cursor()
+    try:
+        dbobj.execute(curs, 'DELETE FROM person_phonetics WHERE person_id=%s',
+                      (person_id,))
+        for mp in itertools.chain(*encode_phones(*names)):
+            dbobj.execute(curs, 'INSERT INTO person_phonetics VALUES (%s, %s)', 
+                          (person_id, mp))
+    finally:
+        curs.close()
+
+def find(query, *names):
+    for name in names:
+        if name and dbobj.is_wild(name):
+            raise ValueError('Phonetic searching does not support wildcards')
+    word_phones = encode_phones(*names)
+    if not word_phones:
+        return
+    for i, dmps in enumerate(word_phones):
+        if i:
+            subquery = subquery.intersect_query()
+        else:
+            subquery = query.in_select('person_id', 'person_phonetics')
+        subquery.where_in('phonetics', dmps)
diff --git a/casemgr/globals.py b/casemgr/globals.py
new file mode 100644
index 0000000..7d0ea89
--- /dev/null
+++ b/casemgr/globals.py
@@ -0,0 +1,35 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import config
+from cocklebur import dbobj
+from cocklebur import form_ui
+from casemgr.notification.client import dummy_notification_client
+
+__all__ = 'db', 'formlib'
+
+# Application globals
+dbobj.execute_debug(config.tracedb)
+db = dbobj.get_db(os.path.join(config.cgi_target, 'db'), config.dsn)
+formlib = form_ui.FormLibXMLDB(db, 'form_defs')
+remote_host = None
+notify = dummy_notification_client()
+
+class Error(Exception): pass        # catchall for informational error messages
+class ReviewForm(Error): pass
diff --git a/casemgr/handle_exception.py b/casemgr/handle_exception.py
new file mode 100644
index 0000000..46216bd
--- /dev/null
+++ b/casemgr/handle_exception.py
@@ -0,0 +1,123 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+import sys
+import config
+import albatross
+import sendmail
+import notify
+import version
+
+def simple_template(req, template_path, template_name, locals_dict = {}):
+    class LocalsDict:
+        def __init__(self, locals_dict):
+            self.__dict__ = locals_dict
+
+    req.set_status(albatross.HTTP_INTERNAL_SERVER_ERROR)
+    req.write_header('Content-Type', 'text/html')
+    req.end_headers()
+    tmp_ctx = albatross.SimpleContext(template_path)
+    tmp_ctx.locals = LocalsDict(locals_dict)
+    templ = tmp_ctx.load_template(template_name)
+    templ.to_html(tmp_ctx)
+    tmp_ctx.flush_content()
+
+
+class ExcMsg(list):
+    def safeadd(self, label, value):
+        if not value:
+            value = '[unknown]'
+        if len(value) > 200:
+            value = value[:200] + '...'
+        self.append('  %-15s: %s' % (label, value))
+            
+
+def session_limit():
+    limit = (config.session_timeout / 60.0) or 20
+    if limit >= 120:
+        return '%.0f hours' % (limit / 60.0)
+    return '%.0f minutes' % limit
+
+
+class HandleExceptionMixin:
+
+    def handle_exception(self, ctx, req):
+        user = rights = page_stack = deploy_mode = None
+        double_trap = True
+        if ctx:
+            if ctx.locals.__page__:
+                page_stack = '>'.join(ctx.locals.__pages__ + [ctx.locals.__page__])
+            double_trap = getattr(ctx.locals, 'double_trap', True)
+            if hasattr(ctx.locals, '_credentials'):
+                creds = ctx.locals._credentials
+                user = creds.user.username
+#                rights = str(creds.rights)
+            deploy_mode = getattr(ctx.locals, 'deploy_mode', None)
+            if not config.debug:
+                self.remove_session(ctx)
+        exc_type = sys.exc_info()[0]
+        if exc_type == albatross.SessionExpired:
+            simple_template(req, 'pages', 'nocookies.html', vars(config))
+        elif exc_type == albatross.ServerError:
+            simple_template(req, 'pages', 'shutdown.html', vars(config))
+        else:
+            try:
+                body = ExcMsg()
+                body.append('Environment:')
+                body.safeadd('User', user)
+#                body.safeadd('User rights', rights)
+                body.safeadd('Remote IP', req.get_remote_host())
+                body.safeadd('User agent', req.get_user_agent())
+                body.safeadd('App vers', version.__version__)
+                body.safeadd('SVN rev', version.__svnrev__)
+                body.safeadd('Py vers', sys.version.replace('\n', ''))
+                body.safeadd('Albatross vers', albatross.__version__)
+                body.safeadd('Page stack', page_stack)
+                body.safeadd('Deploy mode', deploy_mode)
+                pyexc, htmlexc = self.format_exception()
+                body.append('')
+                body.append(htmlexc)
+                body.append('')
+                body.append(pyexc)
+            except:
+                body = None
+                sys.stderr.write('Uncaught exception, exception in '
+                                'exception handler: %s\n' % sys.exc_info()[1])
+            else:
+                sys.stderr.write('\n'.join(body))
+
+#            double_trap = 1
+            if not double_trap:
+                try:
+                    # Albatross needs to expose method to clear the page stack
+                    ctx.locals.__page__ = None
+                    ctx.locals.__pages__ = []
+                    ctx.add_error('WARNING - An application error has occurred, attempting to continue...')
+                    ctx.set_page('main')
+                    ctx.reset_content()
+                    self.load_page(ctx)
+                    self.display_response(ctx)
+                    self.save_session(ctx)
+                    ctx.flush_content()
+                except Exception:
+                    double_trap = True
+            if double_trap:
+                simple_template(req, 'pages', 'traceback.html', vars(config))
+
+            if body:
+                notify.exception_notify(body)
diff --git a/casemgr/logview.py b/casemgr/logview.py
new file mode 100644
index 0000000..921bc1f
--- /dev/null
+++ b/casemgr/logview.py
@@ -0,0 +1,116 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj
+
+from casemgr import globals, paged_search, unituser
+
+
+class LogColumns:
+    def __init__(self, name, label):
+        self.name = name
+        self.label = label
+        self.wide = (self.name == 'event_type')
+
+    def pretty_row(self, row):
+        value = getattr(row, self.name)
+        if self.label == 'Date':
+            return value.date()
+        elif self.label == 'Time':
+            return value.time()
+        elif self.label == 'User':
+            return row.username
+        else:
+            return value
+
+
+class LogView(paged_search.SortablePagedSearch):
+    table = 'user_log'
+
+    def __init__(self, prefs, title, 
+                 user_id=None, case_id=None):
+        query = globals.db.query(self.table, order_by='event_timestamp')
+        if user_id is not None:
+            query.where('user_id = %s', user_id)
+        if case_id is not None:
+            query.where('case_id = %s', case_id)
+        paged_search.SortablePagedSearch.__init__(self, globals.db, prefs, 
+                                                  title=title, query=query)
+
+    def cols(self):
+        return [LogColumns(*c) for c in self.headers]
+
+    def ncols(self):
+        return len(self.headers)
+
+
+class LogViewWithUser(LogView):
+
+    def page_rows(self):
+        rows = paged_search.SortablePagedSearch.page_rows(self)
+        unituser.users.fetch(*[log.user_id for log in rows])
+        for row in rows:
+            row.username = unituser.users[row.user_id].username
+        return rows
+
+
+class SystemLogView(LogViewWithUser):
+    table = 'admin_log'
+    headers = [
+        ('event_timestamp', 'Date'),
+        ('event_timestamp', 'Time'),
+        ('user_id', 'User'),
+        ('remote_addr', 'Remote IP'),
+        ('forwarded_addr', 'Forwarded IP'),
+        ('event_type', 'Event'),
+    ]
+
+
+class AdminLogView(LogView):
+    headers = [
+        ('event_timestamp', 'Date'),
+        ('event_timestamp', 'Time'),
+        ('case_id', 'Case ID'),
+        ('remote_addr', 'Remote IP'),
+        ('forwarded_addr', 'Forwarded IP'),
+        ('event_type', 'Event'),
+    ]
+
+
+class UserLogView(LogView):
+    headers = [
+        ('event_timestamp', 'Date'),
+        ('event_timestamp', 'Time'),
+        ('case_id', 'Case ID'),
+        ('event_type', 'Event'),
+    ]
+
+
+class CaseLogView(LogViewWithUser):
+    headers = [
+        ('event_timestamp', 'Date'),
+        ('event_timestamp', 'Time'),
+        ('user_id', 'User'),
+        ('case_id', 'Case ID'),
+        ('event_type', 'Event'),
+    ]
diff --git a/casemgr/mergelabels.py b/casemgr/mergelabels.py
new file mode 100644
index 0000000..ed905b0
--- /dev/null
+++ b/casemgr/mergelabels.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+class MergeLabels:
+    """
+    Manage a set of name/label pairs, merging labels when names collide.
+    """
+    def __init__(self):
+        self.label_map = {}
+        self.name_order = []
+
+    def add(self, name, label):
+        try:
+            other = self.label_map[name]
+        except KeyError:
+            self.name_order.append(name)
+        else:
+            if other != label:
+                label = '%s/%s' % (other, label)
+        self.label_map[name] = label
+
+    def addall(self, pairs):
+        for name, label in pairs:
+            self.add(name, label)
+
+    def in_order(self):
+        return [(name, self.label_map[name]) for name in self.name_order]
+
+    def label_order(self):
+        pairs = [(self.label_map[name], name) 
+                 for name in self.name_order if name]
+        pairs.sort()
+        pairs = [(name, label) for label, name in pairs]
+        if '' in self.label_map:
+            pairs.insert(0, ('', self.label_map['']))
+        return pairs
diff --git a/casemgr/messages.py b/casemgr/messages.py
new file mode 100644
index 0000000..eaea9f3
--- /dev/null
+++ b/casemgr/messages.py
@@ -0,0 +1,83 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+lvlmap = {
+    'error': 'err',
+    'err': 'err',
+    'warning': 'warn',
+    'warn': 'warn',
+    'information': 'info',
+    'info': 'info',
+}
+
+class Messages(Exception):
+
+    def __init__(self):
+        self.clear_messages()
+
+    def clear_messages(self):
+        self.__have_errors = False
+        self.__messages = []
+
+    def msg(self, lvl, msg):
+        lvl = lvlmap.get(lvl, 'warn')
+        self.__messages.append((lvl, str(msg)))
+        if lvl == 'err':
+            self.__have_errors = True
+
+    message = msg
+
+    def add_messages(self, msgobj):
+        for lvl, msg in msgobj.get_messages():
+            self.msg(lvl, msg)
+
+    def have_errors(self):
+        return self.__have_errors
+
+    def get_messages(self):
+        try:
+            return self.__messages
+        finally:
+            self.clear_messages()
+
+
+class MessageMixin(object):
+    
+    def clear_messages(self):
+        self.__messages = Messages()
+
+    def add_error(self, msg):
+#        import traceback, sys
+#        print >> sys.stderr, 'ERR: %s' % msg
+#        traceback.print_exc()
+        self.__messages.msg('error', msg)
+
+    def add_message(self, msg):
+        self.__messages.msg('info', msg)
+
+    def msg(self, lvl, msg):
+        self.__messages.msg(lvl, msg)
+
+    def add_messages(self, msgobj):
+        self.__messages.add_messages(msgobj)
+
+    def have_errors(self):
+        return self.__messages.have_errors()
+
+    def get_messages(self):
+        return self.__messages.get_messages()
diff --git a/casemgr/nickcache.py b/casemgr/nickcache.py
new file mode 100644
index 0000000..e03bf09
--- /dev/null
+++ b/casemgr/nickcache.py
@@ -0,0 +1,38 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, cached
+
+class NickCache(cached.NotifyCache):
+    notification_target = 'nicknames'
+
+    def load(self):
+        query = globals.db.query('nicknames')
+        nickmap = {}
+        for nick, alt in query.fetchcols(('nick', 'alt')):
+            nickmap.setdefault(nick, []).append(alt)
+        self.nickmap = nickmap
+
+    def get_nicks(self, words):
+        self.refresh()
+        wordnicks = []
+        for word in words:
+            wordnicks.append([word] + self.nickmap.get(word, []))
+        return wordnicks
+
+get_nicks = NickCache().get_nicks
diff --git a/casemgr/nicknames.py b/casemgr/nicknames.py
new file mode 100644
index 0000000..076ec7f
--- /dev/null
+++ b/casemgr/nicknames.py
@@ -0,0 +1,913 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# List of common nicknames culled from the Internet with a number of local
+# additions, etc.
+nicknames = [
+    ('abbie', 'abigail'),
+    ('abby', 'abigail'),
+    ('abe', 'abel'),
+    ('abe', 'abraham'),
+    ('abe', 'abram'),
+    ('ada', 'edith'),
+    ('addie', 'adeline'),
+    ('aggie', 'agatha'),
+    ('aggie', 'agnes'),
+    ('aggy', 'agnes'),
+    ('al', 'albert'),
+    ('alec', 'alexander'),
+    ('alex', 'alexander'),
+    ('alex', 'alexandra'),
+    ('alex', 'alexis'),
+    ('alexie', 'alexander'),
+    ('alexie', 'alexandra'),
+    ('alexie', 'alexis'),
+    ('alf', 'alfred'),
+    ('alf', 'alfredo'),
+    ('alfie', 'alfred'),
+    ('alfie', 'alfredo'),
+    ('algy', 'algernon'),
+    ('alison', 'alice'),
+    ('allie', 'alana'),
+    ('allie', 'alberta'),
+    ('allie', 'alexandra'),
+    ('allie', 'alice'),
+    ('allie', 'alicia'),
+    ('ally', 'alice'),
+    ('ally', 'alicia'),
+    ('andy', 'andrea'),
+    ('andy', 'andrew'),
+    ('angie', 'angelia'),
+    ('annabel', 'annabella'),
+    ('annette', 'ann'),
+    ('annette', 'anna'),
+    ('annette', 'anne'),
+    ('annette', 'hannah'),
+    ('annie', 'ann'),
+    ('annie', 'anna'),
+    ('annie', 'anne'),
+    ('annie', 'hannah'),
+    ('archie', 'archibald'),
+    ('archy', 'archibald'),
+    ('augustin', 'augustine'),
+    ('austin', 'augustine'),
+    ('bab', 'barbara'),
+    ('babbie', 'barbara'),
+    ('babette', 'barbara'),
+    ('babette', 'elizabeth'),
+    ('babs', 'barbara'),
+    ('baldie', 'archibald'),
+    ('barb', 'barbara'),
+    ('barnaby', 'barnabas'),
+    ('barney', 'barnaby'),
+    ('barney', 'bernard'),
+    ('bart', 'bartholomew'),
+    ('bart', 'barton'),
+    ('bartie', 'bartholomew'),
+    ('bartie', 'barton'),
+    ('bastian', 'sebastian'),
+    ('bat', 'bartholomew'),
+    ('bea', 'beatrice'),
+    ('beatrix', 'beatrice'),
+    ('beau', 'beauregard'),
+    ('beck', 'rebecca'),
+    ('beck', 'rebekah'),
+    ('becky', 'rebecca'),
+    ('becky', 'rebekah'),
+    ('bel', 'arabella'),
+    ('bel', 'isabeau'),
+    ('bel', 'isabel'),
+    ('bel', 'isabella'),
+    ('bel', 'isobel'),
+    ('bella', 'arabella'),
+    ('bella', 'isabeau'),
+    ('bella', 'isabel'),
+    ('bella', 'isabella'),
+    ('bella', 'isobel'),
+    ('belle', 'arabella'),
+    ('belle', 'isabelle'),
+    ('ben', 'benjamin'),
+    ('bennet', 'benedict'),
+    ('benny', 'benjamin'),
+    ('bernie', 'bernard'),
+    ('bert', 'albert'),
+    ('bert', 'bertram'),
+    ('bertie', 'albert'),
+    ('bertie', 'bertha'),
+    ('bertie', 'bertram'),
+    ('berty', 'bertha'),
+    ('bess', 'elisabeth'),
+    ('bess', 'elizabeth'),
+    ('bessie', 'elisabeth'),
+    ('bessie', 'elizabeth'),
+    ('bessy', 'elisabeth'),
+    ('bessy', 'elizabeth'),
+    ('beth', 'elisabeth'),
+    ('beth', 'elizabeth'),
+    ('betsy', 'elisabeth'),
+    ('betsy', 'elizabeth'),
+    ('betty', 'elisabeth'),
+    ('betty', 'elizabeth'),
+    ('bex', 'rebecca'),
+    ('bex', 'rebekah'),
+    ('biddy', 'bridget'),
+    ('bill', 'william'),
+    ('billie', 'william'),
+    ('billy', 'william'),
+    ('bob', 'robert'),
+    ('bob', 'rupert'),
+    ('bobbie', 'robert'),
+    ('bobbie', 'roberta'),
+    ('bobby', 'robert'),
+    ('bobby', 'rupert'),
+    ('brad', 'bradley'),
+    ('caddie', 'caroline'),
+    ('caitlin', 'kathleen'),
+    ('cal', 'calvin'),
+    ('carrie', 'caroline'),
+    ('casey', 'cassandra'),
+    ('casy', 'catherine'),
+    ('casy', 'katherine'),
+    ('cathie', 'catherine'),
+    ('cathie', 'katherine'),
+    ('cathy', 'catherine'),
+    ('celine', 'marceline'),
+    ('charley', 'charles'),
+    ('charlie', 'charles'),
+    ('cherry', 'charity'),
+    ('cherry', 'cherie'),
+    ('chris', 'christian'),
+    ('chris', 'christopher'),
+    ('chrissie', 'christina'),
+    ('christie', 'christian'),
+    ('christie', 'christine'),
+    ('christie', 'christopher'),
+    ('christy', 'christian'),
+    ('christy', 'christine'),
+    ('christy', 'christopher'),
+    ('cindy', 'cynthia'),
+    ('cindy', 'lucinda'),
+    ('cis', 'cecilia'),
+    ('cis', 'cecily'),
+    ('cissie', 'cecilia'),
+    ('cissie', 'cecily'),
+    ('cissy', 'cecilia'),
+    ('cissy', 'cecily'),
+    ('cissy', 'priscilla'),
+    ('claire', 'clara'),
+    ('claire', 'clarice'),
+    ('claire', 'clarissa'),
+    ('clare', 'clara'),
+    ('clare', 'clarice'),
+    ('clare', 'clarissa'),
+    ('claud', 'claudius'),
+    ('clement', 'clementina'),
+    ('clement', 'clemintine'),
+    ('clint', 'clinton'),
+    ('colette', 'nicole'),
+    ('colin', 'colombus'),
+    ('colin', 'nicholas'),
+    ('connie', 'constance'),
+    ('cora', 'corinna'),
+    ('dan', 'daniel'),
+    ('danny', 'daniel'),
+    ('dave', 'david'),
+    ('davy', 'david'),
+    ('debbie', 'deborah'),
+    ('denis', 'dennis'),
+    ('denys', 'dennis'),
+    ('di', 'diana'),
+    ('dick', 'richard'),
+    ('dickie', 'richard'),
+    ('dicky', 'richard'),
+    ('die', 'diana'),
+    ('die', 'dinah'),
+    ('dod', 'george'),
+    ('doddy', 'george'),
+    ('dodie', 'dorothy'),
+    ('dolly', 'dorothea'),
+    ('dolly', 'dorothy'),
+    ('don', 'donald'),
+    ('donnie', 'donald'),
+    ('dora', 'dorothea'),
+    ('dora', 'dorothy'),
+    ('dora', 'theodora'),
+    ('doug', 'douglas'),
+    ('drew', 'andrew'),
+    ('eck', 'alexander'),
+    ('ecky', 'alexander'),
+    ('ed', 'edmund'),
+    ('ed', 'edward'),
+    ('ed', 'edwin'),
+    ('eda', 'edith'),
+    ('eddie', 'edmund'),
+    ('eddie', 'edward'),
+    ('eddie', 'edwin'),
+    ('eddy', 'edward'),
+    ('eddy', 'edwin'),
+    ('effie', 'euphemia'),
+    ('elisa', 'elisabeth'),
+    ('eliza', 'elizabeth'),
+    ('ella', 'eleanor'),
+    ('ella', 'elinor'),
+    ('ella', 'helen'),
+    ('ella', 'leonora'),
+    ('ellen', 'eleanor'),
+    ('ellen', 'elinor'),
+    ('ellen', 'helen'),
+    ('ellen', 'leonora'),
+    ('ellie', 'eleanor'),
+    ('ellie', 'ella'),
+    ('ellie', 'ellen'),
+    ('ellie', 'helen'),
+    ('elsa', 'elizabeth'),
+    ('elsie', 'alice'),
+    ('elsie', 'alicia'),
+    ('elsie', 'alison'),
+    ('elsie', 'elisabeth'),
+    ('elsie', 'elizabeth'),
+    ('elsie', 'elspeth'),
+    ('elspie', 'elspeth'),
+    ('emm', 'emeline'),
+    ('emm', 'emily'),
+    ('emm', 'emma'),
+    ('emm', 'emmeline'),
+    ('emmie', 'emeline'),
+    ('emmie', 'emily'),
+    ('emmie', 'emma'),
+    ('emmie', 'emmeline'),
+    ('eneas', 'aeneas'),
+    ('essie', 'esther'),
+    ('essie', 'hester'),
+    ('essie', 'hesther'),
+    ('etta', 'henrietta'),
+    ('eula', 'eulalia'),
+    ('evelina', 'eva'),
+    ('evelina', 'eve'),
+    ('eveline', 'eva'),
+    ('eveline', 'eve'),
+    ('evelyn', 'eva'),
+    ('evelyn', 'eve'),
+    ('fannie', 'frances'),
+    ('fanny', 'frances'),
+    ('faustine', 'faustina'),
+    ('flo', 'florence'),
+    ('flossie', 'florence'),
+    ('floy', 'florence'),
+    ('francie', 'francis'),
+    ('francine', 'frances'),
+    ('frank', 'francis'),
+    ('frankie', 'frances'),
+    ('frankie', 'francis'),
+    ('frankie', 'frank'),
+    ('fred', 'alfred'),
+    ('fred', 'frederic'),
+    ('fred', 'frederick'),
+    ('freddie', 'alfred'),
+    ('freddie', 'frederic'),
+    ('freddie', 'frederica'),
+    ('freddie', 'frederick'),
+    ('freddy', 'frederic'),
+    ('freddy', 'frederick'),
+    ('gab', 'gabriel'),
+    ('gab', 'gabriella'),
+    ('gabby', 'gabriel'),
+    ('gabby', 'gabriella'),
+    ('gabe', 'gabriel'),
+    ('gail', 'abigail'),
+    ('gatty', 'gertrude'),
+    ('gayle', 'abigail'),
+    ('gene', 'eugene'),
+    ('gene', 'genevieve'),
+    ('genie', 'eugenia'),
+    ('geordie', 'george'),
+    ('georgie', 'george'),
+    ('georgie', 'georgiana'),
+    ('georgie', 'georgina'),
+    ('gerard', 'gerald'),
+    ('german', 'germaine'),
+    ('gertie', 'gertrude'),
+    ('gia', 'gianna'),
+    ('gil', 'gilbert'),
+    ('gina', 'angelina'),
+    ('gina', 'giorgina'),
+    ('gina', 'luigina'),
+    ('gina', 'virginia'),
+    ('ginette', 'genevieve'),
+    ('ginger', 'virginia'),
+    ('greg', 'gregory'),
+    ('gregg', 'gregory'),
+    ('greta', 'margaret'),
+    ('gretchen', 'margaret'),
+    ('grissel', 'griselda'),
+    ('gus', 'augustus'),
+    ('gussie', 'augusta'),
+    ('gussie', 'augustus'),
+    ('gussie', 'gus'),
+    ('gussie', 'gustava'),
+    ('gustus', 'augustus'),
+    ('gwen', 'guinevere'),
+    ('gwen', 'gwendolyn'),
+    ('harry', 'harold'),
+    ('harry', 'henry'),
+    ('hattie', 'harriet'),
+    ('hatty', 'harriet'),
+    ('hatty', 'harriot'),
+    ('heidi', 'adelaide'),
+    ('heidi', 'adelheid'),
+    ('hen', 'henry'),
+    ('henny', 'henry'),
+    ('hetty', 'henrietta'),
+    ('hob', 'robert'),
+    ('hodge', 'roger'),
+    ('hodgekin', 'roger'),
+    ('horace', 'horatio'),
+    ('hughie', 'hugh'),
+    ('hughie', 'hugo'),
+    ('humph', 'humphrey'),
+    ('humph', 'humphry'),
+    ('ike', 'isaac'),
+    ('ike', 'izaak'),
+    ('inez', 'agnes'),
+    ('inigo', 'ignatius'),
+    ('isa', 'isabel'),
+    ('isa', 'isabella'),
+    ('isa', 'isobel'),
+    ('iva', 'ivana'),
+    ('jack', 'john'),
+    ('jackie', 'jacqueline'),
+    ('jackie', 'john'),
+    ('jacquetta', 'jacqueline'),
+    ('jacqui', 'jacqueline'),
+    ('jacqui', 'jacques'),
+    ('jake', 'jacob'),
+    ('jamie', 'james'),
+    ('jan', 'jane'),
+    ('janet', 'jane'),
+    ('janie', 'jane'),
+    ('jay', 'jack'),
+    ('jay', 'jacob'),
+    ('jay', 'james'),
+    ('jay', 'jason'),
+    ('jay', 'jasper'),
+    ('jean', 'jane'),
+    ('jean', 'jeannette'),
+    ('jeanette', 'jane'),
+    ('jeanette', 'jeanne'),
+    ('jeanie', 'jane'),
+    ('jeanne', 'jeannette'),
+    ('jeannette', 'jane'),
+    ('jeannette', 'jeanne'),
+    ('jeannie', 'jane'),
+    ('jeannie', 'jean'),
+    ('jeannie', 'jeanne'),
+    ('jeff', 'geoffrey'),
+    ('jeff', 'jeffery'),
+    ('jeff', 'jeffrey'),
+    ('jen', 'jane'),
+    ('jen', 'janet'),
+    ('jen', 'jennifer'),
+    ('jenna', 'jane'),
+    ('jenna', 'jennifer'),
+    ('jennie', 'jane'),
+    ('jennie', 'janet'),
+    ('jennie', 'jennifer'),
+    ('jenny', 'jane'),
+    ('jenny', 'janet'),
+    ('jenny', 'jennifer'),
+    ('jeremias', 'jeremiah'),
+    ('jeremy', 'jeremiah'),
+    ('jerry', 'gerald'),
+    ('jerry', 'gerard'),
+    ('jerry', 'jeremy'),
+    ('jerry', 'jerome'),
+    ('jess', 'jessie'),
+    ('jessie', 'jane'),
+    ('jessie', 'jessica'),
+    ('jill', 'jillian'),
+    ('jim', 'james'),
+    ('jimmie', 'james'),
+    ('jimmy', 'james'),
+    ('jo', 'joseph'),
+    ('jo', 'josephine'),
+    ('jo', 'joanne'),
+    ('joan', 'johanna'),
+    ('joanna', 'johanna'),
+    ('jock', 'john'),
+    ('jody', 'josephine'),
+    ('jody', 'judith'),
+    ('joe', 'joseph'),
+    ('joey', 'joseph'),
+    ('johnnie', 'john'),
+    ('johnny', 'john'),
+    ('jon', 'jonathan'),
+    ('jonah', 'jonas'),
+    ('josh', 'joshua'),
+    ('josie', 'josephine'),
+    ('juanita', 'juana'),
+    ('judy', 'judith'),
+    ('jule', 'julian'),
+    ('jule', 'julius'),
+    ('julia', 'juliet'),
+    ('julie', 'juliet'),
+    ('juliet', 'julie'),
+    ('kate', 'catherine'),
+    ('kate', 'katherine'),
+    ('kath', 'kathleen'),
+    ('kathie', 'catherine'),
+    ('kathie', 'katherine'),
+    ('kathleen', 'catherine'),
+    ('kathleen', 'katherine'),
+    ('kathy', 'katherine'),
+    ('katie', 'catherine'),
+    ('katie', 'katherine'),
+    ('katrine', 'catherine'),
+    ('katrine', 'katherine'),
+    ('kay', 'katherine'),
+    ('ken', 'kenneth'),
+    ('kenny', 'kenneth'),
+    ('kester', 'christopher'),
+    ('kim', 'joachim'),
+    ('kim', 'kimball'),
+    ('kim', 'kimberly'),
+    ('kit', 'catherine'),
+    ('kit', 'christopher'),
+    ('kit', 'katherine'),
+    ('kitty', 'catherine'),
+    ('kitty', 'katherine'),
+    ('krista', 'kristina'),
+    ('kristi', 'kristina'),
+    ('kristie', 'kristina'),
+    ('kristy', 'kristina'),
+    ('kurt', 'conrad'),
+    ('lana', 'alana'),
+    ('lance', 'lancelot'),
+    ('larry', 'laurence'),
+    ('larry', 'lawrence'),
+    ('larry', 'lorenzo'),
+    ('laura', 'laurinda'),
+    ('laurie', 'laura'),
+    ('laurie', 'laurence'),
+    ('len', 'leonard'),
+    ('lena', 'helen'),
+    ('lena', 'helena'),
+    ('lena', 'magdalena'),
+    ('lena', 'magdalene'),
+    ('lenny', 'leonard'),
+    ('lettie', 'letitia'),
+    ('lettie', 'lettice'),
+    ('lew', 'lewis'),
+    ('lew', 'louis'),
+    ('lew', 'ludovic'),
+    ('lewie', 'lewis'),
+    ('lewie', 'louis'),
+    ('lewie', 'ludovic'),
+    ('lex', 'alexander'),
+    ('lexie', 'alexander'),
+    ('liam', 'william'),
+    ('libby', 'elisabeth'),
+    ('libby', 'elizabeth'),
+    ('lilly', 'lilian'),
+    ('lily', 'lilian'),
+    ('linda', 'belinda'),
+    ('linda', 'melinda'),
+    ('lisa', 'elisabeth'),
+    ('lisa', 'elizabeth'),
+    ('liz', 'elisabeth'),
+    ('liz', 'elizabeth'),
+    ('liza', 'elisabeth'),
+    ('liza', 'elizabeth'),
+    ('lizzie', 'elisabeth'),
+    ('lizzie', 'elizabeth'),
+    ('llewelyn', 'llewellyn'),
+    ('llywelyn', 'llewellyn'),
+    ('lola', 'dolores'),
+    ('lonnie', 'alonso'),
+    ('loretta', 'laura'),
+    ('lorinda', 'laurinda'),
+    ('lou', 'lewis'),
+    ('lou', 'louis'),
+    ('lou', 'louisa'),
+    ('lou', 'louise'),
+    ('lou', 'ludovic'),
+    ('louie', 'lewis'),
+    ('louie', 'louis'),
+    ('louie', 'louisa'),
+    ('louie', 'louise'),
+    ('louie', 'ludovic'),
+    ('lucia', 'lucinda'),
+    ('lucy', 'lucinda'),
+    ('luke', 'lucas'),
+    ('lynette', 'lynn'),
+    ('lynnette', 'lynn'),
+    ('mab', 'amabel'),
+    ('mab', 'mabel'),
+    ('mabel', 'amabel'),
+    ('maddie', 'madeline'),
+    ('madge', 'margaret'),
+    ('mag', 'margaret'),
+    ('magda', 'magdalene'),
+    ('maggie', 'margaret'),
+    ('maidie', 'margaret'),
+    ('maisie', 'margaret'),
+    ('mamie', 'marion'),
+    ('mamie', 'mary'),
+    ('mamie', 'miriam'),
+    ('mandy', 'amanda'),
+    ('manny', 'emmanuel'),
+    ('marcellus', 'marcius'),
+    ('marcellus', 'marcus'),
+    ('marcellus', 'mark'),
+    ('marge', 'margaret'),
+    ('margery', 'margaret'),
+    ('margie', 'margaret'),
+    ('marian', 'marianne'),
+    ('marietta', 'maria'),
+    ('marjorie', 'margaret'),
+    ('marjory', 'margaret'),
+    ('marlon', 'marcel'),
+    ('marlon', 'marcelon'),
+    ('marlon', 'marcus'),
+    ('marty', 'martha'),
+    ('marty', 'martin'),
+    ('maryann', 'marianne'),
+    ('mat', 'martha'),
+    ('mat', 'mathilda'),
+    ('mat', 'matilda'),
+    ('mat', 'matthew'),
+    ('mat', 'matthias'),
+    ('matt', 'matthew'),
+    ('matt', 'matthias'),
+    ('mattie', 'martha'),
+    ('mattie', 'mathilda'),
+    ('mattie', 'matilda'),
+    ('mattie', 'matthew'),
+    ('matty', 'martha'),
+    ('matty', 'mathilda'),
+    ('matty', 'matilda'),
+    ('matty', 'matthew'),
+    ('maud', 'madeline'),
+    ('maud', 'magdalene'),
+    ('maud', 'mathilda'),
+    ('maud', 'matilda'),
+    ('maudlin', 'madeline'),
+    ('maureen', 'mary'),
+    ('max', 'maximilian'),
+    ('max', 'maxwell'),
+    ('may', 'mary'),
+    ('meg', 'margaret'),
+    ('megan', 'margaret'),
+    ('meggy', 'margaret'),
+    ('mel', 'melanie'),
+    ('mel', 'melissa'),
+    ('mel', 'melvin'),
+    ('melicent', 'millicent'),
+    ('meta', 'margaret'),
+    ('mia', 'maria'),
+    ('micky', 'michael'),
+    ('mike', 'michael'),
+    ('milicent', 'millicent'),
+    ('mina', 'wilhelmena'),
+    ('mindy', 'melinda'),
+    ('minella', 'wilhelmena'),
+    ('minnie', 'mary'),
+    ('minnie', 'minerva'),
+    ('minnie', 'minna'),
+    ('minnie', 'miriam'),
+    ('minnie', 'wilhelmena'),
+    ('minnie', 'wilhelmina'),
+    ('moll', 'mary'),
+    ('moll', 'miriam'),
+    ('mollie', 'mary'),
+    ('molly', 'mary'),
+    ('molly', 'miriam'),
+    ('mose', 'moses'),
+    ('mosey', 'moses'),
+    ('nabby', 'abigail'),
+    ('nan', 'ann'),
+    ('nan', 'anna'),
+    ('nan', 'anne'),
+    ('nan', 'hannah'),
+    ('nan', 'nancy'),
+    ('nance', 'nancy'),
+    ('nancy', 'ann'),
+    ('nancy', 'anna'),
+    ('nancy', 'anne'),
+    ('nancy', 'hannah'),
+    ('nanny', 'ann'),
+    ('nanny', 'anna'),
+    ('nanny', 'anne'),
+    ('nanny', 'hannah'),
+    ('natasha', 'natalie'),
+    ('nathan', 'nathanael'),
+    ('nathaniel', 'nathanael'),
+    ('neal', 'neil'),
+    ('ned', 'edmund'),
+    ('ned', 'edward'),
+    ('neddy', 'edmund'),
+    ('neddy', 'edward'),
+    ('nell', 'eleanor'),
+    ('nell', 'elinor'),
+    ('nell', 'helen'),
+    ('nell', 'helena'),
+    ('nell', 'leonora'),
+    ('nellie', 'eleanor'),
+    ('nellie', 'elinor'),
+    ('nellie', 'ellen'),
+    ('nellie', 'helen'),
+    ('nellie', 'helena'),
+    ('nellie', 'leonora'),
+    ('nessie', 'agnes'),
+    ('nessie', 'vanessa'),
+    ('nettie', 'antoinette'),
+    ('nettie', 'henrietta'),
+    ('nettie', 'janet'),
+    ('netty', 'antoinette'),
+    ('nic', 'nicholas'),
+    ('nic', 'nicolas'),
+    ('nick', 'nicholas'),
+    ('nick', 'nicolas'),
+    ('nina', 'ann'),
+    ('nina', 'anna'),
+    ('nina', 'anne'),
+    ('nina', 'antonia'),
+    ('nina', 'hannah'),
+    ('nina', 'nancy'),
+    ('noll', 'oliver'),
+    ('nollie', 'oliver'),
+    ('nolly', 'oliver'),
+    ('nonie', 'norah'),
+    ('nonie', 'hanora'),
+    ('nora', 'eleanor'),
+    ('nora', 'elinor'),
+    ('nora', 'helen'),
+    ('nora', 'honaria'),
+    ('nora', 'honora'),
+    ('nora', 'leonora'),
+    ('norah', 'eleanor'),
+    ('norah', 'elinor'),
+    ('norah', 'helen'),
+    ('norah', 'honaria'),
+    ('norah', 'honora'),
+    ('norah', 'leonora'),
+    ('ollie', 'olive'),
+    ('ollie', 'oliver'),
+    ('ollie', 'olivia'),
+    ('olly', 'oliver'),
+    ('osmund', 'osmond'),
+    ('oswold', 'oswald'),
+    ('padriag', 'patrick'),
+    ('paddy', 'patrick'),
+    ('paddy', 'patricius'),
+    ('pam', 'pamela'),
+    ('pat', 'martha'),
+    ('pat', 'mathilda'),
+    ('pat', 'matilda'),
+    ('pat', 'patricia'),
+    ('pat', 'patricius'),
+    ('pat', 'patrick'),
+    ('patsy', 'patricia'),
+    ('patti', 'patricia'),
+    ('patty', 'martha'),
+    ('patty', 'mathilda'),
+    ('patty', 'matilda'),
+    ('patty', 'patricia'),
+    ('paul', 'paulinus'),
+    ('paula', 'paulina'),
+    ('paulette', 'paula'),
+    ('pauline', 'paulina'),
+    ('paulus', 'paulinus'),
+    ('peg', 'margaret'),
+    ('peggy', 'margaret'),
+    ('penny', 'penelope'),
+    ('perry', 'peregrin'),
+    ('pete', 'peter'),
+    ('peterkin', 'peter'),
+    ('phamie', 'euphemia'),
+    ('phebe', 'phoebe'),
+    ('phemie', 'euphemia'),
+    ('pheny', 'josephine'),
+    ('phil', 'philibert'),
+    ('phil', 'philip'),
+    ('phineas', 'phinehas'),
+    ('phyl', 'phillis'),
+    ('phyl', 'phyllis'),
+    ('pip', 'philip'),
+    ('pip', 'philippa'),
+    ('pippa', 'philippa'),
+    ('pliny', 'plinius'),
+    ('pol', 'mary'),
+    ('pol', 'miriam'),
+    ('polly', 'mary'),
+    ('polly', 'miriam'),
+    ('prudy', 'prudence'),
+    ('prue', 'prudence'),
+    ('quentin', 'quintin'),
+    ('rab', 'raibert'),
+    ('randy', 'randall'),
+    ('randy', 'randolph'),
+    ('ray', 'raymond'),
+    ('ray', 'raymund'),
+    ('reg', 'reginald'),
+    ('reg', 'reynold'),
+    ('rick', 'richard'),
+    ('rick', 'roderic'),
+    ('rick', 'roderick'),
+    ('rickey', 'richard'),
+    ('ricky', 'richard'),
+    ('rita', 'margarita'),
+    ('rob', 'robert'),
+    ('rob', 'rupert'),
+    ('robbie', 'robert'),
+    ('robbie', 'rupert'),
+    ('robin', 'robert'),
+    ('robin', 'rupert'),
+    ('robyn', 'roberta'),
+    ('rod', 'roderic'),
+    ('rod', 'roderick'),
+    ('roddie', 'roderic'),
+    ('roddie', 'roderick'),
+    ('roddy', 'roderic'),
+    ('roddy', 'roderick'),
+    ('rodge', 'roger'),
+    ('rodolph', 'rodolphus'),
+    ('roland', 'rowland'),
+    ('ron', 'ronald'),
+    ('ronnie', 'ronald'),
+    ('rosetta', 'rosa'),
+    ('rosie', 'rosa'),
+    ('rosie', 'rosabel'),
+    ('rosie', 'rosabella'),
+    ('rosie', 'rosalia'),
+    ('rosie', 'rosalie'),
+    ('rosie', 'rosalind'),
+    ('rosie', 'rose'),
+    ('roxy', 'roxana'),
+    ('rudolph', 'rodolphus'),
+    ('rudolphus', 'rodolphus'),
+    ('rudy', 'rudolf'),
+    ('sacha', 'aleksandr'),
+    ('sal', 'sally'),
+    ('sal', 'sarah'),
+    ('sally', 'sarah'),
+    ('sam', 'samantha'),
+    ('sam', 'samuel'),
+    ('sammy', 'samuel'),
+    ('samson', 'sampson'),
+    ('sanders', 'alexander'),
+    ('sandra', 'alexandra'),
+    ('sandy', 'alexander'),
+    ('sandy', 'alexandra'),
+    ('seb', 'sebastian'),
+    ('sereno', 'serenus'),
+    ('sheri', 'cherie'),
+    ('sherri', 'cherie'),
+    ('sherry', 'cherie'),
+    ('sibella', 'isabeau'),
+    ('sibella', 'isabel'),
+    ('sibella', 'isabella'),
+    ('sibella', 'isobel'),
+    ('sibyl', 'sibylla'),
+    ('silas', 'silvanus'),
+    ('sim', 'simeon'),
+    ('sim', 'simon'),
+    ('sis', 'cecilia'),
+    ('sis', 'cecily'),
+    ('sisely', 'cecilia'),
+    ('sisely', 'cecily'),
+    ('sissy', 'cecilia'),
+    ('sissy', 'priscilla'),
+    ('sol', 'solomon'),
+    ('sonia', 'sophia'),
+    ('sonja', 'sophia'),
+    ('sonya', 'sophia'),
+    ('sophie', 'sophia'),
+    ('sophy', 'sophia'),
+    ('stacey', 'anastacsa'),
+    ('stacey', 'eustace'),
+    ('stacy', 'anastacsa'),
+    ('stacy', 'eustace'),
+    ('stella', 'estelle'),
+    ('steve', 'stephen'),
+    ('steve', 'steven'),
+    ('stevie', 'stephen'),
+    ('sue', 'susan'),
+    ('sue', 'susanna'),
+    ('sue', 'susannah'),
+    ('suke', 'susan'),
+    ('suke', 'susanna'),
+    ('suke', 'susannah'),
+    ('suky', 'susan'),
+    ('suky', 'susanna'),
+    ('suky', 'susannah'),
+    ('susie', 'susan'),
+    ('susie', 'susanna'),
+    ('susie', 'susannah'),
+    ('susy', 'susan'),
+    ('susy', 'susanna'),
+    ('susy', 'susannah'),
+    ('tam', 'thomas'),
+    ('tammie', 'tamara'),
+    ('tammie', 'thomas'),
+    ('tammy', 'tamara'),
+    ('tammy', 'thomasina'),
+    ('tanya', 'tatiana'),
+    ('tasha', 'natalia'),
+    ('tasha', 'natasha'),
+    ('tave', 'octavius'),
+    ('tave', 'octavus'),
+    ('tavy', 'octavius'),
+    ('tavy', 'octavus'),
+    ('ted', 'edward'),
+    ('ted', 'theodore'),
+    ('teddy', 'edward'),
+    ('teddy', 'theodore'),
+    ('teenie', 'christina'),
+    ('terri', 'theresa'),
+    ('terry', 'terence'),
+    ('terry', 'teresa'),
+    ('terry', 'theresa'),
+    ('tib', 'isabeau'),
+    ('tib', 'isabel'),
+    ('tib', 'isabella'),
+    ('tib', 'isobel'),
+    ('tibbie', 'isabeau'),
+    ('tibbie', 'isabel'),
+    ('tibbie', 'isabella'),
+    ('tibbie', 'isobel'),
+    ('tilda', 'mathilda'),
+    ('tilda', 'matilda'),
+    ('tillie', 'mathilda'),
+    ('tillie', 'matilda'),
+    ('tim', 'timothy'),
+    ('timmy', 'timothy'),
+    ('tina', 'christina'),
+    ('toby', 'tobiah'),
+    ('toby', 'tobias'),
+    ('tom', 'thomas'),
+    ('tommie', 'thomas'),
+    ('tommy', 'thomas'),
+    ('toni', 'antonia'),
+    ('tony', 'anthony'),
+    ('tony', 'antony'),
+    ('tonya', 'antonia'),
+    ('tottie', 'charlotte'),
+    ('tracie', 'theresa'),
+    ('tricia', 'patricia'),
+    ('tristam', 'tristram'),
+    ('trixie', 'beatrice'),
+    ('trudy', 'gertrude'),
+    ('ty', 'tyler'),
+    ('ty', 'tyrone'),
+    ('ty', 'tyrus'),
+    ('val', 'valentine'),
+    ('val', 'valerie'),
+    ('vest', 'silvester'),
+    ('vest', 'sylvester'),
+    ('vester', 'silvester'),
+    ('vester', 'sylvester'),
+    ('vic', 'victor'),
+    ('vicki', 'victoria'),
+    ('vickie', 'victoria'),
+    ('vicky', 'victoria'),
+    ('vinty', 'vincent'),
+    ('wat', 'walter'),
+    ('watty', 'walter'),
+    ('will', 'wilbert'),
+    ('will', 'willard'),
+    ('will', 'william'),
+    ('willie', 'william'),
+    ('willy', 'william'),
+    ('wilmett', 'wilhelmena'),
+    ('wilmot', 'wilhelmena'),
+    ('winnie', 'winfred'),
+    ('winnie', 'winifred'),
+    ('xander', 'alexander'),
+    ('xina', 'christina'),
+    ('yorick', 'george'),
+    ('zach', 'zachariah'),
+    ('zach', 'zechariah'),
+    ('zacky', 'zachariah'),
+    ('zacky', 'zechariah'),
+    ('zak', 'zachariah'),
+    ('zak', 'zechariah'),
+    ('zana', 'zaina'),
+    ('zana', 'suzanne'),
+    ('zebedee', 'zebadiah'),
+    ('zeke', 'ezekial'),
+]
diff --git a/casemgr/notification/__init__.py b/casemgr/notification/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/casemgr/notification/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/casemgr/notification/client.py b/casemgr/notification/client.py
new file mode 100644
index 0000000..c5a9f28
--- /dev/null
+++ b/casemgr/notification/client.py
@@ -0,0 +1,177 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# System libraries
+import sys
+import os
+import socket
+import errno
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+# Application modules
+from socketcore import SocketCore
+from daemon import notification_daemon
+
+def exec_daemon(host, port=None):
+    pid = os.fork()
+    if pid:
+        # Parent (forks again after init, and intermediate exits)
+        os.waitpid(pid, 0)
+    else:
+        thisdir = os.path.dirname(__file__)
+        daemonpath = os.path.join(thisdir, 'daemon.py')
+        pypath=os.path.abspath(os.path.join(thisdir, os.pardir, os.pardir))
+        args = ['python', daemonpath, '--bg', '--listen', host]
+        if port:
+            args.append('--port')
+            args.append(port)
+        env=dict(PYTHONPATH=pypath)
+        os.execve(sys.executable, args, env)
+        sys._exit(1)
+
+
+class dummy_notification_client:
+
+    def poll(self):
+        pass
+
+    def notify(self, *event):
+        pass
+
+    def subscribe(self, event, callback):
+        return False
+
+
+class notification_client(SocketCore):
+
+    def __init__(self):
+        SocketCore.__init__(self)
+        self.sock = None
+        self.subscriptions = {}
+        self.sent_subscriptions = set()
+
+    def connect(self):
+        self.sock.setblocking(0)
+        self.sent_subscriptions = set()
+        self.poll()
+
+    def proc_line(self, line):
+        words = line.split()
+        if words[0] == '!':
+            callbacks = self.subscriptions.get(words[2])
+            if callbacks:
+                for callback in callbacks:
+                    callback(*words[3:])
+
+    def subscribe(self, event, callback):
+        if self.sock is None:
+            self.connect()
+        try:
+            self.subscriptions[event].add(callback)
+        except KeyError:
+            callbacks = self.subscriptions[event] = set()
+            callbacks.add(callback)
+        return True
+
+    def notify(self, *event):
+        # Notifications should only be sent after transactions are committed to
+        # the database, or a quick client may fetch the data before the
+        # transaction is committed.
+        if self.sock is None:
+            self.connect()
+        self.write('notify %s\n' % ' '.join(map(str, event)))
+        self.poll()
+
+    def poll(self):
+        if self.sock is None:
+            self.connect()
+        want_subscriptions = set(self.subscriptions)
+        add = want_subscriptions - self.sent_subscriptions
+        if add:
+            self.write('subscribe %s\n' % ','.join(add))
+            self.sent_subscriptions = want_subscriptions
+        SocketCore.poll(self)
+
+
+class unix_notification_client(notification_client):
+    def __init__(self, path):
+        notification_client.__init__(self)
+        self.path = os.path.join(path, 'db', 'notification', 'socket')
+
+    def connect(self):
+        sockdir = os.path.dirname(self.path)
+        try:
+            os.mkdir(sockdir)
+        except OSError, (eno, estr):
+            if eno != errno.EEXIST:
+                raise
+        else:
+            os.chmod(sockdir, 0300)
+        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        try:
+            self.sock.connect(self.path)
+        except socket.error, (eno, estr):
+            if eno == errno.ECONNREFUSED:
+                # Daemon has died without cleaning up? This is racy...
+                self.unlink()
+            elif eno != errno.ENOENT:
+                self.sock.close()
+                raise
+            exec_daemon(self.path)
+            self.sock.connect(self.path)
+        notification_client.connect(self)
+
+    def unlink(self):
+        try:
+            os.unlink(self.path)
+        except OSError:
+            pass
+
+
+class inet_notification_client(notification_client):
+    def __init__(self, host, port):
+        notification_client.__init__(self)
+        self.host = host
+        self.port = port
+
+    def connect(self):
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            self.sock.connect((self.host, self.port))
+        except socket.error:
+            self.sock.close()
+            raise
+        notification_client.connect(self)
+
+
+def connect(path, host, port):
+    if host == 'none':
+        return dummy_notification_client()
+    try:
+        if host == 'local':
+            return unix_notification_client(path)
+        else:
+            return inet_notification_client(host, port)
+    except Exception:
+        import sys, traceback
+        traceback.print_exc()
+        print >> sys.stderr, 'WARNING: notification client startup failed, falling back on time-based cache expiry'
+        return dummy_notification_client()
diff --git a/casemgr/notification/daemon.py b/casemgr/notification/daemon.py
new file mode 100644
index 0000000..544d744
--- /dev/null
+++ b/casemgr/notification/daemon.py
@@ -0,0 +1,263 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# System libraries
+import os
+import sys
+import socket
+import select
+import errno
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur.daemonize import daemonize
+
+from socketcore import SocketCore
+
+
+class Client(SocketCore):
+    def __init__(self, registry, sock, addr):
+        self.registry = registry
+        SocketCore.__init__(self, sock, addr)
+        self.write('+ OK\n')
+
+    def write_start(self):
+        self.registry.write_start(self)
+
+    def write_stop(self):
+        self.registry.write_stop(self)
+
+    def read_start(self):
+        self.registry.read_start(self)
+
+    def read_stop(self):
+        self.registry.read_stop(self)
+
+    def close_event(self):
+        self.close()
+        self.registry.client_close(self)
+
+    def proc_line(self, line):
+        words = line.split()
+        if words:
+            cmd = words.pop(0)
+            try:
+                meth = getattr(self, 'do_' + cmd)
+            except AttributeError:
+                self.write('- Unknown command %r\n' % cmd)
+            else:
+                try:
+                    meth(*words)
+                except SystemExit:
+                    raise
+                except Exception, e:
+                    self.write('- %s\n' % e)
+
+    def notify(self, *args):
+        self.write('! NOTIFY %s\n' % (' '.join(args)))
+
+    def monitor(self, msg):
+        self.write('! MONITOR %s\n' % msg)
+
+    def do_subscribe(self, events):
+        self.write('+ OK\n')
+        self.registry.subscribe(self, events)
+
+    def do_notify(self, *args):
+        self.write('+ OK\n')
+        self.registry.notify(self, *args)
+
+    def do_list(self):
+        for evname in self.registry.seen:
+            self.write('%s\n' % evname)
+        self.write('+ OK\n')
+
+    def do_subscribers(self):
+        for evname, clients in self.registry.events.iteritems():
+            self.write('%s\n' % evname)
+            for client in clients:
+                self.write('  %s\n' % (client.addr,))
+        self.write('+ OK\n')
+        
+    def do_help(self):
+        self.write('notify <event>,...       Send notifications for events\n')
+        self.write('subscribe <event>,...    Subscribe to events\n')
+        self.write('list                     List events\n')
+        self.write('help                     This help text\n')
+        self.write('+ OK\n')
+
+    def do_monitor(self, state):
+        if state == 'on':
+            self.registry.add_monitor(self)
+        elif state == 'off':
+            self.registry.del_monitor(self)
+        else:
+            self.write('! monitor <on|off>\n')
+            return
+        self.write('+ OK\n')
+
+    def do_quit(self):
+        self.close()
+
+    def do_shutdown(self):
+        self.log('Received SHUTDOWN from %s' % (self.addr,))
+        sys.exit(0)
+
+
+class Registry:
+    def __init__(self, sock):
+        self.events = {}
+        self.sock = sock
+        self.clients = set()
+        self.read_start(self)
+        self.writers = set()
+        self.seen = set()
+        self.monitors = set()
+
+    def fileno(self):
+        return self.sock.fileno()
+
+    def write_start(self, client):
+        self.writers.add(client)
+
+    def write_stop(self, client):
+        self.writers.discard(client)
+
+    def read_start(self, client):
+        self.clients.add(client)
+
+    def read_stop(self, client):
+        self.clients.discard(client)
+
+    def read_event(self):
+        s, addr = self.sock.accept()
+        s.setblocking(0)
+        Client(self, s, addr)
+
+    def add_monitor(self, client):
+        self.monitors.add(client)
+
+    def del_monitor(self, client):
+        self.monitors.discard(client)
+
+    def monitor(self, msg):
+        for client in self.monitors:
+            client.monitor(msg)
+
+    def subscribe(self, client, events):
+        if self.monitors:
+            self.monitor('subscribe from %s, events %s' % (client.addr, events))
+        for evname in events.split(','):
+            try:
+                event = self.events[evname]
+            except KeyError:
+                event = self.events[evname] = set()
+            event.add(client)
+
+    def notify(self, client, evname, *args):
+        if self.monitors:
+            self.monitor('notify from %s, event %s, args %s' % 
+                            (client.addr, evname, args))
+        self.seen.add(evname)
+        event = self.events.get(evname)
+        if event:
+            for client in event:
+                client.notify(evname, *args)
+
+    def client_close(self, client):
+        self.del_monitor(client)
+        for event in self.events.itervalues():
+            event.discard(client)
+
+    def poll(self):
+        r, w, e = select.select(list(self.clients), list(self.writers), [], None)
+        for o in r:
+            o.read_event()
+        for o in w:
+            o.write_event()
+
+
+def notification_daemon(address_family, address, want_background=True):
+    l = socket.socket(address_family, socket.SOCK_STREAM)
+    l.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    l.setblocking(0)
+    try:
+        l.bind(address)
+        l.listen(5)
+    except socket.error, (eno, estr):
+        if eno == errno.EADDRINUSE:
+            l.close()
+            return
+        raise
+    if want_background and daemonize():
+        l.close()
+        return                  # Parent
+    try:
+        registry = Registry(l)
+        while 1:
+            registry.poll()
+    finally:
+        try:
+            l.close()
+        except socket.error:
+            pass
+        if address_family == AF_UNIX:
+            try:
+                os.unlink(address)
+            except OSError:
+                pass
+    sys.exit(0)
+
+
+def kill_daemon(addr, port):
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    try:
+        s.connect((addr, port))
+    except socket.error, (eno, estr):
+        if eno != errno.ECONNREFUSED:
+            sys.exit('%s port %s: %s' % (addr, port, estr))
+    else:
+        s.send('shutdown\n')
+        s.close()
+
+
+if __name__ == '__main__':
+    import optparse
+    parser = optparse.OptionParser()
+    parser.add_option('-l', '--listen', default='0.0.0.0',
+                      help='Listen address (default ALL)', metavar='ADDR')
+    parser.add_option('-p', '--port', type='int', default=13535,
+                      help='port to listen on')
+    parser.add_option('--background', '--bg', 
+                      default=False, action='store_true',
+                      help='Daemonise (background and dissociate from tty)')
+    parser.add_option('--kill', default=False, action='store_true',
+                      help='kill running daemon (if any)')
+    options, args = parser.parse_args()
+    if options.listen.startswith('/'):
+        af = socket.AF_UNIX
+        address = options.listen
+    else:
+        af = socket.AF_INET
+        address = (options.listen, options.port)
+    if options.kill:
+        kill_daemon('127.0.0.1', options.port)
+    else:
+        notification_daemon(af, address, want_background=options.background)
diff --git a/casemgr/notification/socketcore.py b/casemgr/notification/socketcore.py
new file mode 100644
index 0000000..761cdf4
--- /dev/null
+++ b/casemgr/notification/socketcore.py
@@ -0,0 +1,109 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+import socket
+import select
+import errno
+
+class SocketCore:
+    def __init__(self, sock=None, addr=None):
+        self.sock = sock
+        self.addr = addr
+        self.wrbuf = ''
+        self.rdbuf = ''
+        if sock:
+            self.read_start()
+
+    def fileno(self):
+        return self.sock.fileno()
+
+    def write(self, data):
+        if data and not self.wrbuf:
+            self.write_start()
+        self.wrbuf += data
+
+    def write_start(self):
+        "Add the socket to the write select list"
+
+    def write_stop(self):
+        "Remove the socket to the write select list"
+
+    def read_start(self):
+        "Add the socket to the read select list"
+
+    def read_stop(self):
+        "Remove the socket to the read select list"
+
+    def close(self):
+        if self.sock is not None:
+            self.read_stop()
+            self.write_stop()
+            try:
+                self.sock.close()
+            except socket.error, (eno, estr):
+                self.log('%s: close: %s' % (self.addr, estr))
+            self.sock = None
+
+    def close_event(self):
+        self.close()
+
+    def write_event(self):
+        "Socket ready for write"
+        if self.sock is not None:
+            try:
+                n = self.sock.send(self.wrbuf)
+            except socket.error, (eno, estr):
+                if eno == errno.EAGAIN:
+                    return
+                self.log('%s: send: %s' % (self.addr, estr))
+                return self.close_event()
+            self.wrbuf = self.wrbuf[n:]
+            if not self.wrbuf:
+                self.write_stop()
+
+    def read_event(self):
+        "Socket ready for read"
+        if self.sock is not None:
+            try:
+                buf = self.sock.recv(8192)
+            except socket.error, (eno, estr):
+                if eno == errno.EAGAIN:
+                    return
+                if eno != errno.ECONNRESET:
+                    self.log('%s: recv: %s' % (self.addr, estr))
+                return self.close_event()
+            if not buf:
+                return self.close_event()
+            lines = (self.rdbuf + buf).split('\n')
+            self.rdbuf = lines.pop(-1)
+            for line in lines:
+                self.proc_line(line)
+
+    def poll(self):
+        w = []
+        if self.wrbuf:
+            w = [self.sock]
+        r, w, e = select.select([self.sock], w, [], 0)
+        if r:
+            self.read_event()
+        if w:
+            self.write_event()
+        
+    def log(cls, msg):
+        print >> sys.stderr, '%s.%s: %s' % (cls.__module__, cls.__name__, msg)
+    log = classmethod(log)
diff --git a/casemgr/notify.py b/casemgr/notify.py
new file mode 100644
index 0000000..651be5e
--- /dev/null
+++ b/casemgr/notify.py
@@ -0,0 +1,89 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard libs
+import time
+import re
+import os
+import email
+
+# Application modules
+from cocklebur import template
+from casemgr.sendmail import Sendmail
+
+import config
+
+
+
+def load_template(name):
+    path = os.path.join(config.cgi_target, 'mail', name)
+    f = open(path)
+    try:
+        return f.read()
+    finally:
+        f.close()
+
+
+def send_template(name, o=None, **kw):
+    tmpl = load_template(name)
+    tmpl = template.expand_template(tmpl, o, **kw)
+    sm = email.message_from_string(tmpl, Sendmail)
+    sm.send()
+
+
+def register_notify(user):
+    if hasattr(config, 'registration_notify') and config.registration_notify:
+        sponsor = None
+        if user.sponsoring_user_id:
+            import unituser
+            sponsor_user = unituser.users[user.sponsoring_user_id]
+            sponsor = '%s (%s)' % (sponsor_user.fullname, sponsor_user.username)
+        try:
+            send_template('registration_notify', o=user, sponsor=sponsor)
+        except:
+            pass
+
+
+def exception_notify(body):
+    if hasattr(config, 'exception_notify') and config.exception_notify:
+        try:
+            send_template('exception_notify', exception='\n'.join(body))
+        except:
+            pass
+
+
+def too_bad_notify(user):
+    if hasattr(config, 'registration_notify') and config.registration_notify:
+        try:
+            send_template('too_many_attempts', o=user)
+        except:
+            pass
+
+
+def register_invite(ctx, user):
+    url = '[not available]'
+    host = ctx.request.get_param('SERVER_NAME')
+    ssl = ctx.request.get_param('HTTPS')
+    uri = ctx.request.get_uri()
+    if host and uri:
+        if ssl:
+            protocol = 'https'
+        else:
+            protocol = 'http'
+        url = '%s://%s%s?invite=%s' % (protocol, host, uri, user.enable_key)
+    send_template('register_invite', o=user, url=url)
diff --git a/casemgr/paged_search.py b/casemgr/paged_search.py
new file mode 100644
index 0000000..614aada
--- /dev/null
+++ b/casemgr/paged_search.py
@@ -0,0 +1,271 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard Lib
+import time
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+# Application
+from cocklebur import dbobj
+
+class Pager(object):
+    results_per_page = 10
+    prev_pager = None
+
+    def reset_page(self):
+        self.go_page = None
+        self.page = 1
+
+    def result_count(self):
+        # This must be overridden
+        return 0
+
+    def page_length(self):
+        # Because CGI field merging will turn ints into strs.
+        return int(self.results_per_page)
+
+    def pages(self):
+        results_per = self.page_length()
+        return (self.result_count() + results_per - 1) / results_per
+
+    def has_pages(self):
+        return self.pages() > 1
+
+    def cur_page(self):
+        return self.page
+
+    def page_jump(self):
+        if self.go_page:
+            for p in self.go_page:
+                p = int(p[len('Page '):])
+                if p != self.page and 1 <= p <= self.pages():
+                    self.page = p
+                    break
+            self.go_page = None
+
+    def has_prev(self):
+        return self.page > 1
+
+    def has_next(self):
+        return self.page < self.pages()
+
+    def prev(self):
+        self.go_page = None
+        if self.has_prev():
+            self.page -= 1
+
+    def next(self):
+        self.go_page = None
+        if self.has_next():
+            self.page += 1
+
+    def do(self, cmd, *args):
+        # New-style dispatch, but can't handle page jumps?
+        if cmd == 'prev':
+            self.prev()
+        elif cmd == 'next':
+            self.next()
+        elif cmd == 'orderby':
+            self.set_order_by(*args)
+
+    def page_process(self, ctx):
+        # Old-style co-operative dispatch
+        if ctx.req_equals('search_reset'):
+            self.reset()
+        elif ctx.req_equals('results_prev_page'):
+            self.prev()
+        elif ctx.req_equals('results_next_page'):
+            self.next()
+        else:
+            self.page_jump()
+            return False
+        return True
+
+    def page_list(self):
+        return ['Page %s' % (i + 1) for i in range(self.pages())]
+
+
+class PagerSelect(Pager):
+
+    def __init__(self):
+        self.selected = set()
+        self.page_selected = []
+
+    def _selected_to_page(self):
+        self.page_selected = [i for i, key in enumerate(self.page_pkeys())
+                              if key in self.selected]
+
+    def _page_to_selected(self):
+        page_keys = self.page_pkeys()
+        self.selected.difference_update(page_keys)
+        for idx in self.page_selected:
+            self.selected.add(page_keys[int(idx)])
+
+    def select(self, selected):
+        self.selected = set(selected)
+        self._selected_to_page()
+
+    def do(self, cmd, *args):
+        self._page_to_selected()
+        if cmd == 'select_all':
+            self.selected = set(self.pkeys)
+        elif cmd == 'select_none':
+            self.selected = set()
+        else:
+            Pager.do(self, cmd, *args)
+        self._selected_to_page()
+
+    def page_process(self, ctx):
+        self._page_to_selected()
+        Pager.page_process(self, ctx)
+        self._selected_to_page()
+
+
+class PagedSearch(Pager):
+
+    results_per_page_options = [10, 25, 50, 100]
+
+    def __init__(self, db, prefs, table, title=None, selected=[]):
+        Pager.__init__(self)
+        self.db = db
+        self.prefs = prefs
+        self.table = table
+        self.title = title
+        self.results_per_page = prefs.get('results_per_page')
+        self.reset()
+
+    def reset(self):
+        self.new_search()
+
+    def new_search(self):
+        self.reset_page()
+        self.search_time = 0
+        self.page_time = 0
+        self.pkeys = None
+        self.error = ''
+        self.empty = True
+
+    def __len__(self):
+        if self.pkeys is not None:
+            return len(self.pkeys)
+
+    def set_error(self, err):
+        self.error = str(err)
+        self.pkeys = None
+
+    def fetch_pkeys(self, query):
+        st = time.time()
+        pkeys = []
+        last = None
+        for keys in query.fetchkeys():
+            # Strip duplicate keys if they are consecutive
+            if keys == last:
+                continue
+            pkeys.append(keys)
+            last = keys
+        self.empty = not pkeys
+        if self.empty:
+            self.set_error('Nothing found')
+        self.pkeys = pkeys
+        self.search_time = time.time() - st
+
+    def result_count(self):
+        if self.pkeys is None:
+            return 0
+        return len(self.pkeys)
+
+    def page_pkeys(self):
+        if self.pkeys is not None:
+            page_len = self.page_length()
+            start = (self.cur_page() - 1) * page_len
+            end = start + page_len
+            return self.pkeys[start:end]
+
+    def page_rows(self):
+        query = self.db.query(self.table)
+        return query.fetchall_by_keys(self.page_pkeys())
+
+    def result_page(self, *args):
+        st = time.time() 
+        rows = []
+        if not self.error:
+            rows = self.page_rows(*args)
+        self.page_time = time.time() - st
+        return rows
+
+
+class SortablePagedSearch(PagedSearch):
+
+    def __init__(self, db, prefs, query=None, title=None):
+        if query is not None:
+            self.query = query
+        PagedSearch.__init__(self, db, prefs, None, title)
+        assert self.query is not None
+        self.table = self.query.table_desc.name
+        self.order_by = self.query.order_by
+        self.fetch_pkeys(self.query)
+
+    def set_order_by(self, order_by):
+        if order_by.endswith('_desc'):
+            order_by = order_by[:-len('_desc')] + ' DESC'
+        self.order_by = self.query.order_by = order_by
+        self.reset()
+
+    def result_count(self):
+        if self.pkeys is None:
+            self.fetch_pkeys(self.query)
+        return PagedSearch.result_count(self)
+
+    def page_pkeys(self):
+        if self.order_by != self.query.order_by:
+            self.query.order_by = self.order_by
+            self.fetch_pkeys(self.query)
+        elif self.pkeys is None:
+            # Refresh
+            self.fetch_pkeys(self.query)
+        return PagedSearch.page_pkeys(self)
+
+    def fetch_pkeys(self, query):
+        try:
+            PagedSearch.fetch_pkeys(self, query)
+        except dbobj.DatabaseError, e:
+            self.db.rollback()
+            self.set_error(e)
+            self.pkeys = []
+
+
+def push_pager(ctx, pager):
+    """
+    We maintain a stack of pagers (implemented as a linked list), as the input
+    names in the page templates are hard-coded (this is mainly a crutch for the
+    page_select macro).
+    """
+    pager.prev_pager = getattr(ctx.locals, 'paged_search', None)
+    ctx.locals.paged_search = pager
+    if pager.prev_pager is None:
+        ctx.add_session_vars('paged_search')
+
+def pop_pager(ctx):
+    paged_search = getattr(ctx.locals, 'paged_search', None)
+    if paged_search is not None:
+        ctx.locals.paged_search = paged_search.prev_pager
+        if ctx.locals.paged_search is None:
+            ctx.del_session_vars('paged_search')
diff --git a/casemgr/person.py b/casemgr/person.py
new file mode 100644
index 0000000..df3f95e
--- /dev/null
+++ b/casemgr/person.py
@@ -0,0 +1,403 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import re
+from mx import DateTime
+from cocklebur import datetime, agelib, dbobj
+from casemgr import globals, fuzzyperson
+import config
+
+Error = dbobj.ValidationError
+
+sexes = [
+    ('', 'Unknown'),
+    ('F', 'Female'),
+    ('M', 'Male'),
+]
+
+def expandsex(value):
+    if value == 'M':
+        return 'Male'
+    elif value == 'F':
+        return 'Female'
+    elif not value:
+        return 'Unknown'
+    else:
+        return value
+
+indigenous_values = [
+    ('', 'Unknown'),
+    ('neither', 'Neither'),
+    ('aboriginal', 'Aboriginal only'),
+    ('aboriginal_tsi', 'Both Aboriginal and Torres Strait Islander'),
+    ('tsi_only', 'Torres Strait Islander only'),
+]
+indigenous_map = dict([(k, v) for k, v in indigenous_values])
+
+
+name_split = re.compile(r'([ ()/:;".,-]+)')
+def soft_titlecase(text):
+    """
+    Titlecase /text/ unless /text/ is already (partially) mixed case.
+    When titlecasing, apply special rules for things like "POBOX" and
+    "O'Reilly".
+    """
+    casemap = {
+        'po': 'PO',
+        'gpo': 'GPO',
+        'rmb': 'RMB',
+        'pobox': 'PO Box',
+        'von': 'von',
+    }
+    text = text.strip()
+    words = name_split.split(text)
+    for word in words:
+        if word.istitle():
+            return text
+    result = []
+    while len(words):
+        word = words.pop(0)
+        if word.islower() or word.isupper():
+            word = word.capitalize()
+            if word.startswith("O'"):
+                word = "O'" + word[2:].capitalize()
+        word = casemap.get(word.lower(), word)
+        result.append(word)
+        try:
+            sep = words.pop(0)
+        except IndexError:
+            pass
+        else:
+            result.append(sep)
+    return ''.join(result)
+
+
+class Person(object):
+
+    person_attrs = ('data_src',
+                    'surname', 'given_names', 'interpreter_req', 
+                    'DOB', 'DOB_prec', 'sex', 
+                    'home_phone', 'work_phone', 'mobile_phone', 'fax_phone',
+                    'e_mail', 
+                    'street_address', 'locality', 
+                    'state', 'postcode', 'country',
+                    'alt_street_address', 'alt_locality', 
+                    'alt_state', 'alt_postcode', 'alt_country',
+                    'work_street_address', 'work_locality', 
+                    'work_state', 'work_postcode', 'work_country',
+                    'occupation',
+                    'person_id',
+                    'passport_number', 'passport_country',
+                    'passport_number_2', 'passport_country_2',
+                    'indigenous_status')
+
+    def __init__(self):
+        self.__clear_attrs()
+
+    def from_person(self, seed_person):
+        # seed_person might be a dbrow or another Person instance.
+        self._copy_attrs(seed_person, self)
+        dob = getattr(seed_person, 'DOB_edit', None)
+        if dob:
+            self.DOB_edit = dob
+        else:
+            self.DOB_edit = agelib.from_db(self)
+
+    def __clear_attrs(self):
+        for attr in self.person_attrs:
+            setattr(self, attr, None)
+        self.DOB_edit = None
+
+    def _copy_attrs(self, src, dst):
+        for attr in self.person_attrs:
+            value = getattr(src, attr)
+            if isinstance(value, str) and value == '!':
+                value = None
+            setattr(dst, attr, value)
+
+    # NOTE - for "add" optionexpr's, null means "Unknown",
+    # but for "search" optionexpr's, null means "Any" and "!" means "Unknown".
+    # This inconsistency is necessary so that disabled demographic fields have
+    # sensible behaviour, and so when a "search" transitions into an "add", the
+    # field gets a sane default.
+    def to_query(self, db, query, fuzzy=False):
+        def q(query, field):
+            value = getattr(self, field)
+            if value:
+                if value == '!':
+                    query.where('persons.%s IS NULL' % field)
+                else:
+                    query.where('persons.%s ILIKE %%s' % field, 
+                                dbobj.wild(value))
+        self.normalise()
+        q(query, 'data_src')
+        q(query, 'locality')
+        q(query, 'postcode')
+        q(query, 'state')
+        q(query, 'country')
+        q(query, 'alt_locality')
+        q(query, 'alt_postcode')
+        q(query, 'alt_state')
+        q(query, 'alt_country')
+        q(query, 'work_locality')
+        q(query, 'work_postcode')
+        q(query, 'work_state')
+        q(query, 'work_country')
+        q(query, 'occupation')
+        q(query, 'interpreter_req')
+        q(query, 'indigenous_status')
+        or_query = query.sub_expr(conjunction = 'OR')
+        # sex will be not True or M or F
+        sex = self.sex and self.sex.upper() != 'U' and self.sex
+        # names AND dob AND sex
+        if fuzzy and (dbobj.is_wild(self.surname) 
+                            or dbobj.is_wild(self.given_names)):
+            fuzzy = False
+        if fuzzy:
+            and_query = or_query.sub_expr(conjunction = 'AND')
+            if self.surname or self.given_names:
+                try:
+                    fuzzyperson.find(and_query, self.surname, self.given_names)
+                except ValueError, e:
+                    raise Error(str(e))
+            try:
+                agelib.dob_query(and_query, 'persons.DOB', 
+                                 self.DOB, self.DOB_prec)
+            except datetime.Error, e:
+                raise Error('Date of birth/age: %s' % e)
+            if sex:
+                and_query.where('persons.sex ilike %s', self.sex)
+        else:
+            if self.surname or self.given_names or sex or self.DOB:
+                and_query = or_query.sub_expr(conjunction='AND')
+                names = []
+                if self.surname:
+                    names.extend(self.surname.split())
+                if self.given_names:
+                    names.extend(self.given_names.split())
+                if names:
+                    for word in names:
+                        word = dbobj.wild(word)
+                        name_expr = and_query.sub_expr(conjunction='OR')
+                        name_expr.where('surname ILIKE %s', word)
+                        name_expr.where('given_names ILIKE %s', word)
+                try:
+                    agelib.dob_query(and_query, 'persons.DOB', 
+                                     self.DOB, self.DOB_prec)
+                except datetime.Error, e:
+                    raise Error('Date of birth/age: %s' % e)
+                if sex:
+                    and_query.where('persons.sex ilike %s', self.sex)
+        q(or_query, 'home_phone')
+        q(or_query, 'work_phone')
+        q(or_query, 'mobile_phone')
+        q(or_query, 'fax_phone')
+        q(or_query, 'e_mail')
+        q(or_query, 'person_id')
+        q(or_query, 'street_address')
+        q(or_query, 'alt_street_address')
+        q(or_query, 'work_street_address')
+        if self.passport_number:
+            if self.passport_country:
+                and_query = or_query.sub_expr(conjunction = 'AND')
+                q(and_query, 'passport_number')
+                q(and_query, 'passport_country')
+            else:
+                q(or_query, 'passport_number')
+        else:
+            q(or_query, 'passport_country')
+        if self.passport_number_2:
+            if self.passport_country_2:
+                and_query = or_query.sub_expr(conjunction = 'AND')
+                q(and_query, 'passport_number_2')
+                q(and_query, 'passport_country_2')
+            else:
+                q(or_query, 'passport_number_2')
+        else:
+            q(or_query, 'passport_country_2')
+
+    def expandsex(self):
+        return expandsex(self.sex)
+
+    def validate(self):
+        pass
+
+    def normalise(self):
+        try:
+            agelib.to_db(self.DOB_edit, self)
+        except datetime.Error, e:
+            raise Error('Date of birth/age: %s' % e)
+        for attr in self.person_attrs:
+            value = getattr(self, attr)
+            if value:
+                try:
+                    norm_fn = getattr(self, 'normalise_' + attr)
+                except AttributeError:
+                    pass
+                else:
+                    setattr(self, attr, norm_fn(value))
+
+    def _name_upper(self, name):
+        return name.strip().upper()
+
+    normalise_surname = _name_upper
+    normalise_sex = _name_upper
+    normalise_locality = _name_upper
+    normalise_alt_locality = _name_upper
+    normalise_work_locality = _name_upper
+
+    normalise_street_address = staticmethod(soft_titlecase)
+    normalise_alt_street_address = staticmethod(soft_titlecase)
+    normalise_work_street_address = staticmethod(soft_titlecase)
+    normalise_given_names = staticmethod(soft_titlecase)
+
+    def summary(self, order=None):
+        from casemgr import demogfields
+        fields = demogfields.get_demog_fields(globals.db, None)
+        fields = fields.reordered_context_fields(order, 'result')
+        return fields.summary(self)
+
+
+class EditPerson(Person):
+
+    def set_dbrow(self, dbrow):
+        self.__dbrow = dbrow
+        self._copy_attrs(self.__dbrow, self)
+        self.DOB_edit = agelib.from_db(self)
+
+#    def has_changed(self):
+#        for attr in self.person_attrs:
+#            a = getattr(self.__dbrow, attr)
+#            b = getattr(self, attr)
+#            if (a or b) and a != b:
+#                import sys
+#                print >> sys.stderr, '%r %r %r' % (attr, getattr(self.__dbrow, attr), getattr(self, attr))
+#                return True
+#        return False
+
+    def has_changed(self):
+        self.normalise()
+        self._copy_attrs(self, self.__dbrow)
+        try:
+            return self.__dbrow.db_has_changed()
+        finally:
+            self.__dbrow.db_revert()
+
+    def db_desc(self):
+        self.normalise()
+        self._copy_attrs(self, self.__dbrow)
+        try:
+            return self.__dbrow.db_desc()
+        finally:
+            self.__dbrow.db_revert()
+
+    def db_update(self):
+        do_fuzzy_update = (self.surname != self.__dbrow.surname or 
+                           self.given_names != self.__dbrow.given_names)
+        self._copy_attrs(self, self.__dbrow)
+        try:
+            self.__dbrow.db_update()
+        except dbobj.RecordDeleted:
+            raise dbobj.RecordDeleted('%s has been deleted (or merged) by another user' % config.person_label)
+        self._copy_attrs(self.__dbrow, self)
+        self.DOB_edit = agelib.from_db(self)
+        if do_fuzzy_update:
+            fuzzyperson.update(self.__dbrow.db(), self.person_id,
+                               self.surname, self.given_names)
+
+    def db_revert(self):
+        self.__dbrow.db_revert()
+        self._copy_attrs(self.__dbrow, self)
+
+    def db_delete(self):
+        self.__dbrow.db_delete()
+
+
+def person(seed_person=None):
+    """
+    Returns a Person object *not* for editing, optionally filled with fields
+    from /seed_person/.
+    """
+    person = Person()
+    if seed_person is not None:
+        person.from_person(seed_person)
+    return person
+
+
+def edit_row(dbrow):
+    """
+    Returns a Person object for editing representing an existing person given a
+    person table row.
+    """
+    person = EditPerson()
+    person.set_dbrow(dbrow)
+    return person
+
+
+def edit_id(person_id):
+    """
+    Returns a Person object for editing representing an existing person given a
+    person_id
+    """
+    query = globals.db.query('persons')
+    query.where('person_id = %s', person_id)
+    dbrow = query.fetchone()
+    if dbrow is None:
+        raise Error('Person record not found')
+    person = EditPerson()
+    person.set_dbrow(dbrow)
+    return person
+
+
+def edit_new(seed_person=None):
+    """
+    Returns a new Person object for editing, optionally filled with fields from
+    /seed_person/.
+    """
+    person = EditPerson()
+    person.set_dbrow(globals.db.new_row('persons'))
+    if seed_person is not None:
+        person.from_person(seed_person)
+    person.data_src = None
+    return person
+
+
+class DelayLoadPersons:
+
+    def __init__(self, person_cls=Person):
+        self.defered_person_ids = {}
+        self.person_cls = person_cls
+
+    def get(self, person_id):
+        try:
+            return self.defered_person_ids[person_id]
+        except KeyError:
+            inst = self.person_cls()
+            self.defered_person_ids[person_id] = inst
+            return inst
+
+    def load(self, db):
+        """
+        Load all registered persons, return a list of ID's not found
+        """
+        query = db.query('persons')
+        query.where_in('person_id', self.defered_person_ids.keys())
+        for row in query.fetchall():
+            p = self.defered_person_ids.pop(row.person_id)
+            p.from_person(row)
+        return self.defered_person_ids.keys()
diff --git a/casemgr/persondupe.py b/casemgr/persondupe.py
new file mode 100644
index 0000000..717ea96
--- /dev/null
+++ b/casemgr/persondupe.py
@@ -0,0 +1,599 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from __future__ import division
+
+import os, sys
+import math
+from time import time
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj, daemonize, datetime
+from casemgr import globals, persondupecfg
+from casemgr.persondupestat import DupeRunning, dupescan_notify
+
+#    These are the status values currently used:
+STATUS_EXCLUDED = 'E'   # operator excluded
+STATUS_NEW      = 'N'   # new potential dupe
+STATUS_CONFLICT = 'C'   # import conflict
+
+uncertain = 0.5 # XXX somewhat of a fudge
+
+class NGram(object):
+    __slots__ = ('record', 'ngram_count','ngrams', 'matches')
+    N = 3
+
+    def __init__(self, record, row):
+        self.record = record
+        ngrams = set()
+        for field in self.fields:
+            value = getattr(row, field)
+            if value:
+                for word in value.upper().split():
+                    if word == 'UNKNOWN':
+                        continue
+                    word = ' %s ' % word
+                    ngram_count = len(word) - self.N + 1
+                    i = 0
+                    while i < ngram_count:
+                        ngrams.add(intern(word[i:i+self.N]))
+                        i += 1
+        self.matches = None
+        self.ngrams = tuple(ngrams)
+        self.ngram_count = len(self.ngrams)
+        for ngram in self.ngrams:
+            self.ngram_map.setdefault(ngram, []).append(self)
+
+    def prescan(self):
+        shared_counts = {}
+        for ngram in self.ngrams:
+            for other in self.ngram_map[ngram]:
+                if other is not self:
+                    if other in shared_counts:
+                        shared_counts[other] += 2
+                    else:
+                        shared_counts[other] = 2
+        self.matches = {}
+        for other, shared_count in shared_counts.iteritems():
+            ratio = shared_count / (self.ngram_count + other.ngram_count)
+            if ratio > uncertain:
+                self.matches[other] = ratio
+                #assert ratio <= 1.0, 'ratio %.1f, shared cnt %d, this cnt %d, other cnt %d' % (ratio, shared_count, self.ngram_count, other.ngram_count)
+        self.ngrams = None  # No longer needed, save memory
+        return self.matches
+
+    def match(self, other):
+        if not self.ngram_count or not other.ngram_count:
+            return None
+        return self.matches.get(other, 0.0)
+
+
+class Sex(object):
+    __slots__ = ('record', 'sex',)
+    ngram_count = 0
+
+    def __init__(self, record, row):
+        self.record = record
+        self.sex = row.sex
+
+    def prescan(self):
+        pass
+
+    def match(self, other):
+        if self.sex not in ('M', 'F') or other.sex not in ('M', 'F'):
+            return None
+        return ((self.sex == 'M' and other.sex == 'M') or
+                (self.sex == 'F' and other.sex == 'F'))
+
+
+class Age(object):
+    __slots__ = ('record', 'DOB', 'DOB_prec')
+    ngram_count = 0
+
+    def __init__(self, record, row):
+        self.record = record
+        self.DOB = row.DOB
+        self.DOB_prec = row.DOB_prec or 1
+
+    def prescan(self):
+        pass
+
+    def match(self, other):
+        if self.DOB is None or other.DOB is None:
+            return None
+        delta = abs((self.DOB - other.DOB).days)
+        prec = max(self.DOB_prec, other.DOB_prec)
+        return 1 / (delta / prec + 1.0) ** 2
+
+
+
+class Matchers(list):
+    
+    def __str__(self):
+        result = []
+        for matcher in self:
+            attrs = ['weight=%.1f' % matcher.weight]
+            if hasattr(matcher, 'fields'):
+                attrs.append('fields=%r' % matcher.fields)
+            result.append('%s(%s)' % (matcher.__name__, ', '.join(attrs)))
+        return ', '.join(result)
+
+
+def get_matchers(dupepersoncfg):
+    matchers = Matchers()
+    for group in dupepersoncfg.ngram_groups:
+        if group.enabled and group.fields:
+            attrs = {'weight': group.weight, '__slots__': ()}
+            if group.fields == ['sex']:
+                base = Sex
+            elif group.fields == ['DOB']:
+                base = Age
+            else:
+                base = NGram
+                attrs['fields'] = group.fields
+                attrs['N'] = dupepersoncfg.ngram_level
+                attrs['ngram_map'] = {}
+            cls = type(group.label, (base,), attrs)
+            matchers.append(cls)
+    # Adjust relative weights
+    tot_weight = sum([cls.weight for cls in matchers])
+    for cls in matchers:
+        cls.relweight = cls.weight / tot_weight
+    return matchers
+
+
+class Record(object):
+    __slots__ = ('key', 'last_update', 'data', 'likely')
+
+    def __init__(self, row, matchers):
+        self.key = row.person_id
+        self.last_update = row.last_update
+        self.data = [cls(self, row) for cls in matchers]
+        self.likely = set()
+
+    def prescan(self):
+        for mg in self.data:
+            likely = mg.prescan()
+            if likely is not None:
+                for mg in likely:
+                    self.likely.add(mg.record)
+
+    def match(self, other):
+        score = 0.0
+        for a, b in zip(self.data, other.data):
+            s = a.match(b)
+            if s is None:
+                s = uncertain
+            score += s * a.relweight
+#        if score > 0.70:
+#            print >> sys.stderr, self.key, other.key, score
+#            for a, b in zip(self.data, other.data):
+#                print >> sys.stderr, a.__class__.__name__, a.match(b)
+        return score
+
+    def desc_match(self, other):
+        desc = []
+        desc.append('%s:%s' % (self.key, other.key))
+        score = 0.0
+        for a, b in zip(self.data, other.data):
+            s = a.match(b)
+            if s is None:
+                s = uncertain
+            score += s * a.relweight
+            name = a.__class__.__name__
+            desc.append('%s:%.0f%%' % (name, s * 100.0))
+        desc.append('TOTAL:%.0f%%' % (score * 100.0))
+        return ', '.join(desc)
+
+    def ngram_count(self):
+        return sum([data.ngram_count for data in self.data])
+
+
+class MatchPair(object):
+    """
+    This mirrors a row from the /dupe_persons/ table, and records a pair
+    of person Id's that are a likely match, or who have been explicitly
+    excluded.
+    """
+    cols = (
+        'low_person_id', 'high_person_id', 'confidence', 'status',
+        'exclude_reason',
+    )
+    def __init__(self, low_person_id, high_person_id, 
+                 confidence=None, status=STATUS_NEW, exclude_reason=''):
+        assert low_person_id < high_person_id
+        self.low_person_id = low_person_id
+        self.high_person_id = high_person_id
+        self.confidence = confidence
+        self.status = status
+        self.exclude_reason = exclude_reason
+
+    def dbrow(self, db):
+        row = db.new_row('dupe_persons')
+        for col in self.cols:
+            setattr(row, col, getattr(self, col))
+        return row
+
+    def __cmp__(self, other):
+        return cmp(self.confidence, other.confidence)
+
+    def confpc(self):
+        if self.confidence is None:
+            return 'n/a'
+        return '%.0f%%' % (self.confidence * 100)
+
+
+def last_run(db):
+    query = db.query('dupe_persons')
+    return datetime.mx_parse_datetime(query.aggregate('min(timechecked)'))
+
+
+def get_status(id_a, id_b, for_update=False):
+    if id_a > id_b:
+        id_b, id_a = id_a, id_b
+    query = globals.db.query('dupe_persons', for_update=for_update)
+    query.where('low_person_id=%s AND high_person_id=%s', id_a, id_b)
+    row = query.fetchone()
+    if not row:
+        return STATUS_NEW, ''
+    return row.status, row.exclude_reason
+
+
+def set_status(id_a, id_b, status, exclude_reason=None, confidence=None):
+    if id_a > id_b:
+        id_b, id_a = id_a, id_b
+    query = globals.db.query('dupe_persons', for_update=True)
+    query.where('low_person_id=%s AND high_person_id=%s', id_a, id_b)
+    row = query.fetchone()
+    if not row:
+        row = globals.db.new_row('dupe_persons')
+        row.low_person_id, row.high_person_id = id_a, id_b
+    row.status = status
+    row.exclude_reason = exclude_reason
+    if confidence is not None:
+        row.confidence = confidence
+    row.db_update(refetch=False)
+
+
+def exclude(id_a, id_b, reason):
+    set_status(id_a, id_b, STATUS_EXCLUDED, reason)
+
+
+def clear_exclude(id_a, id_b):
+    set_status(id_a, id_b, STATUS_NEW)
+
+
+def conflict(id_a, id_b):
+    set_status(id_a, id_b, STATUS_CONFLICT, confidence=1.0)
+
+
+def dupe_lock(db, mode='SHARE'):
+    try:
+        db.lock_table('dupe_persons', mode, wait=False)
+    except dbobj.DatabaseError:
+        raise DupeRunning
+
+class DupePersons:
+    """
+    In-core representation of the dupe_persons table. 
+    
+    Explicit table locks are used to provide mutual exclusion. The
+    background dupe identification process obtains an EXCLUSIVE lock,
+    while clients briefly obtain a SHARE lock (while loading the
+    table). This does not prevent dupe identification being run while
+    clients are working on dupe data, but does prevent multiple dupe
+    identification runs being started, and the EXCLUSIVE prevents clients
+    from committing exclusion updates, which would otherwise be clobbered.
+    """
+    MAX_MATCHES = 10000
+
+    def __init__(self):
+        self.matchpairs = {}
+        self.load_status = None
+
+    def load(self, db, status=None):
+        query = db.query('dupe_persons')
+        if status is not None:
+            self.load_status = status
+            query.where('status = %s', status)
+        for row in query.fetchcols(MatchPair.cols):
+            mp = MatchPair(*row)
+            key = mp.low_person_id, mp.high_person_id
+            self.matchpairs[key] = mp
+
+    def save(self, db):
+        query = db.query('dupe_persons')
+        query.where('status != %s', STATUS_CONFLICT)
+        query.delete()
+        for mp in self.matchpairs.itervalues():
+            mp.dbrow(db).db_update(False)
+
+    def get(self, id_a, id_b):
+        if id_a > id_b:
+            lowhigh = id_b, id_a
+        else:
+            lowhigh = id_a, id_b
+        mp = self.matchpairs.get(lowhigh)
+        if mp is None:
+            mp = MatchPair(*lowhigh)
+            self.matchpairs[lowhigh] = mp
+        return mp
+
+    def sorted(self):
+        pairs = self.matchpairs.values()
+        pairs.sort()
+        pairs.reverse()
+        return pairs
+
+    def __len__(self):
+        return len(self.matchpairs)
+
+    def adjust_cutoff(self, cutoff):
+        while len(self.matchpairs) > self.MAX_MATCHES and cutoff < 0.9:
+            cutoff += 0.05
+            for mp in self.matchpairs.values():
+                if mp.confidence < cutoff and mp.status == STATUS_NEW:
+                    lowhigh = mp.low_person_id, mp.high_person_id
+                    del self.matchpairs[lowhigh]
+        return cutoff
+
+
+def loaddupe(db):
+    """
+    Load the dupe_persons table data, return a sorted list from best to
+    worst match.
+    """
+    dp = DupePersons()
+    dupe_lock(db)
+    dp.load(db)
+    return dp.sorted()
+
+
+def loadconflicts(db):
+    """
+    Load the dupe_persons table data, return a sorted list from best to
+    worst match.
+    """
+    dp = DupePersons()
+    dp.load(db, status=STATUS_CONFLICT)
+    return dp.sorted()
+
+
+class Timer:
+
+    def __init__(self):
+        self.times = []
+        self.timers = {}
+
+    def start(self, label):
+        self.timers[label] = time()
+
+    def stop(self, label):
+        el = time() - self.timers.pop(label)
+        self.times.append((label, el))
+
+    def __str__(self):
+        times = []
+        for label, el in self.times:
+            if el > 90:
+                times.append('%s: %.1fm' % (label, el / 60.0))
+            else:
+                times.append('%s: %.2fs' % (label, el))
+        return ', '.join(times)
+
+
+class MatchPersons:
+    """
+    This object manages the duplicate person identification.
+
+    The scan compares every person against every other person, and where
+    the match strength is greater than /uncertain/, creates (or updates)
+    a MatchPair record in the /dupes/ structure (which is an instance
+    of DupePersons).
+    """
+
+    def __init__(self, db, config=None, updated_only=False):
+        self.records = []
+        if config is None:
+            config = persondupecfg.new_persondupecfg()
+        self.configure(config)
+        self.dupes = DupePersons()
+        self.timer = Timer()
+        dupe_lock(db, 'EXCLUSIVE')
+        self.load(db, updated_only)
+        self.prescan()
+        self.cross_compare(updated_only)
+
+    def configure(self, dupepersoncfg):
+        self.matchers = get_matchers(dupepersoncfg)
+        self.cutoff = dupepersoncfg.cutoff
+
+    def load(self, db, updated_only):
+        self.timer.start('load')
+        dupescan_notify('load', 0, 0)
+        query = db.query('persons')
+        #query.where('(person_id % 3) = 0') # XXX speed-up for debugging only
+        for row in query.yieldall():
+            self.records.append(Record(row, self.matchers))
+        if updated_only:
+            self.dupes.load(db)
+        else:
+            self.dupes.load(db, status=STATUS_EXCLUDED)
+        self.timer.stop('load')
+        self.last_run = last_run(db)
+
+    def prescan(self):
+        self.timer.start('prescan')
+        last_pc = 0
+        iterations = len(self.records)
+        t0 = time()
+        for n, record in enumerate(self.records):
+            pc = n * 100 // iterations
+            if pc != last_pc:
+                el = time() - t0
+                etc = int(el / n * (iterations - n))
+                dupescan_notify('index', pc, etc)
+                last_pc = pc
+            record.prescan()
+        self.timer.stop('prescan')
+
+    def save(self, db):
+        self.timer.start('save')
+        self.dupes.save(db)
+        self.timer.stop('save')
+
+    def _yield_update_pairs(self):
+        thres = self.last_run
+        updated_records = [r for r in self.records
+                            if not r.last_update or r.last_update >= thres]
+        iterations = len(self.records) * len(updated_records)
+        count = 0
+        checked = set()
+        for a in updated_records:
+            for b in self.records:
+                count += 1
+                if a.key == b.key:
+                    continue
+                if a.key > b.key:
+                    keypair = b.key, a.key
+                else:
+                    keypair = a.key, b.key
+                if keypair in checked:
+                    continue
+                checked.add(keypair)
+                yield count, iterations, a, b
+
+    def _yield_all_pairs(self):
+        records = self.records
+        nrecords = len(records)
+        iterations = (nrecords - 1) * nrecords // 2
+        count = 0
+        for ai in xrange(nrecords-1):
+            for bi in xrange(ai+1, nrecords):
+                count += 1
+                a = records[ai]
+                b = records[bi]
+                yield count, iterations, a, b
+
+    def _yield_likely_pairs(self):
+        iterations = sum([len(a.likely) for a in self.records])
+        count = 0
+        for a in self.records:
+            for b in a.likely:
+                count += 1
+                yield count, iterations, a, b
+
+    def cross_compare(self, updated_only=False):
+        self.timer.start('scan')
+        t0 = time()
+        if updated_only:
+            gen = self._yield_update_pairs
+        else:
+            #gen = self._yield_all_pairs
+            gen = self._yield_likely_pairs
+        thres = self.last_run
+        last_pc = 0
+        for n, iterations, a, b in gen():
+                pc = n * 100 // iterations
+                if iterations > 100000 and pc != last_pc:
+                    el = time() - t0
+                    etc = int(el / n * (iterations - n))
+                    dupescan_notify('scan', pc, etc)
+                    last_pc = pc
+                match_confidence = a.match(b)
+                if match_confidence > self.cutoff:
+                    mp = self.dupes.get(a.key, b.key)
+                    mp.confidence = match_confidence
+                    self.cutoff = self.dupes.adjust_cutoff(self.cutoff)
+        self.timer.stop('scan')
+
+    def ngram_count(self):
+        return sum([rec.ngram_count() for rec in self.records])
+
+    def stats(self):
+        n_ngrams = self.ngram_count()
+        ngram_rate = '??'
+        if self.records:
+            ngram_rate = '%.1f' % (n_ngrams / len(self.records))
+        return 'Times: %s (%d ngrams, %d records, %s ngrams/rec, %d likely matches)' %\
+            (self.timer,
+             n_ngrams, len(self.records), 
+             ngram_rate, len(self.dupes))
+
+    def report(self):
+        pairs = self.dupes.sorted()
+        print 'top %d:' % (min(len(pairs), 20))
+        for mp in pairs[:20]:
+            print '%8d vs %-8d: %5s (%s:%s)' %\
+                (mp.low_person_id, mp.high_person_id, mp.confpc(), 
+                 mp.status, mp.exclude_reason)
+
+
+def explain_dupe(prefs, person_a, person_b):
+    config = prefs.get('persondupecfg')
+    if config is None:
+        config = persondupecfg.new_persondupecfg()
+    matchers = get_matchers(config)
+    #print >> sys.stderr, person_a.surname, person_a.given_names
+    #print >> sys.stderr, person_b.surname, person_b.given_names
+    #print >> sys.stderr, uncertain, matchers
+    a = Record(person_a, matchers)
+    b = Record(person_b, matchers)
+    b.prescan()
+    a.prescan()
+    return a.desc_match(b)
+
+
+def persondupe(db, dup_config, updated_only=False):
+    # We have to close our connection to the database, or the forked child will
+    # inherit it, which is not allowed, and attempting to close one results in
+    # both being torn down.
+    import config
+    db.close()
+    if daemonize.daemonize():
+        return
+    try:
+        try:
+            import psyco
+        except ImportError:
+            pass
+        else:
+            psyco.full()
+        print >> sys.stderr, '%s: Person dupe detection started' % config.appname
+        mp = MatchPersons(db, dup_config, updated_only)
+        mp.save(db)
+        print >> sys.stderr, '%s: Person dupe detection: %s' % (config.appname, mp.stats())
+        db.commit()
+    except DupeRunning:
+        print >> sys.stderr, 'Person dupe detection already running'
+    os._exit(0)
+
+
+if __name__ == '__main__':
+    from casemgr import globals
+    try:
+        mp = MatchPersons(globals.db, None, sys.argv[1] == 'updated')
+        mp.save(globals.db)
+        print mp.stats()
+        mp.report()
+        globals.db.commit()
+    except DupeRunning:
+        sys.exit('Already running')
diff --git a/casemgr/persondupecfg.py b/casemgr/persondupecfg.py
new file mode 100644
index 0000000..0efcce4
--- /dev/null
+++ b/casemgr/persondupecfg.py
@@ -0,0 +1,128 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import copy
+
+from casemgr import globals
+from casemgr import demogfields
+
+class NGramGroup(object):
+    special_fields = ('DOB', 'Sex')
+    def __init__(self, label, weight, fields, editable=True):
+        self.label = label
+        self.weight = weight
+        self.fields = fields
+        self.editable = editable
+        self.enabled = True
+
+    def demogfields(self):
+        fields = demogfields.get_demog_fields(globals.db, None)
+        return [fields.field_by_name(field) for field in self.fields]
+
+    def fields_optionexpr(self):
+        fields = demogfields.get_demog_fields(globals.db, None)
+        return [(field.name, field.label) 
+                for field in fields
+                if (field.entity == 'person'
+                    and field.name not in self.special_fields)]
+
+        self.label = other.label
+        self.weight = other.weight
+        self.fields = list(other.fields)
+
+    def reset(self):
+        try:
+            group_defaults = defaults[self.index]
+        except IndexError:
+            return
+        self.label = group_defaults.label
+        self.weight = group_defaults.weight
+        self.fields = group_defaults.fields
+        self.enabled = group_defaults.enabled
+
+
+defaults = [
+    NGramGroup('Names', 1.0, ['surname', 'given_names']),
+    NGramGroup('Sex', 1.0, ['sex'], editable=False),
+    NGramGroup('Age', 1.0, ['DOB'], editable=False),
+    NGramGroup('Addresses', 1.0, 
+                ['street_address', 'locality',
+                'state', 'postcode', 'country',
+                'alt_street_address', 'alt_locality',
+                'alt_state', 'alt_postcode', 'alt_country',
+                'work_street_address', 'work_locality',
+                'work_state', 'work_postcode', 'work_country']),
+    NGramGroup('Phone', 1.0, 
+                ['home_phone', 'work_phone',
+                'mobile_phone', 'fax_phone', 'e_mail']),
+    NGramGroup('Passport', 1.0, 
+                ['passport_number', 'passport_country',
+                'passport_number_2', 'passport_country_2'])
+]
+
+
+class PersonDupeCfg(object):
+    cutoff_options = [(c / 100.0, '%d%%' % c)
+                        for c in range(55,100,5)]
+
+    def __init__(self):
+        self.ngram_level = 3
+        self.cutoff = 0.5
+        self.ngram_groups = []
+
+    def edit_group(self, index):
+        if index is None:
+            group = NGramGroup('', 1.0, [])
+            group.initial = group
+        else:
+            group = copy.deepcopy(self.ngram_groups[index])
+        group.index = index
+        return group
+
+    def apply_group(self, group):
+        index = group.index
+        if group.fields or (index and index < len(defaults)):
+            del group.index
+            group.enabled = (str(group.enabled) == 'True')
+            group.weight = float(group.weight)
+            if index is None:
+                self.ngram_groups.append(group)
+            else:
+                self.ngram_groups[index] = group
+        elif index:
+            del self.ngram_groups[index]
+
+    def reset(self):
+        self.ngram_level = 3
+        self.cutoff = 0.5
+        self.ngram_groups = copy.deepcopy(defaults)
+
+    def start_editing(self):
+        return copy.deepcopy(self)
+
+    def stop_editing(self):
+        self.ngram_level = int(self.ngram_level)
+        self.cutoff = float(self.cutoff)
+#        self.ngram_groups = [group for group in self.ngram_groups
+#                             if group.fields]
+
+
+def new_persondupecfg():
+    cfg = PersonDupeCfg()
+    cfg.reset()
+    return cfg
diff --git a/casemgr/persondupestat.py b/casemgr/persondupestat.py
new file mode 100644
index 0000000..f7af836
--- /dev/null
+++ b/casemgr/persondupestat.py
@@ -0,0 +1,50 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals
+
+dupescan_phase = dupescan_pc = dupescan_etc = None
+
+def dupescan_event(phase, pc, etc):
+    global dupescan_phase, dupescan_pc, dupescan_etc
+    dupescan_phase = phase
+    dupescan_pc = int(pc)
+    dupescan_etc = int(etc)
+
+def dupescan_subscribe():
+    globals.notify.subscribe('dupescan', dupescan_event)
+
+def dupescan_notify(phase, pc, etc):
+    globals.notify.notify('dupescan', phase, int(pc), int(etc))
+
+class DupeRunning(globals.Error):
+    def __init__(self):
+        globals.notify.poll()
+        msg = 'Duplicate scan in progress'
+        if dupescan_phase:
+            msg += ', %s phase' % dupescan_phase
+        if dupescan_pc and dupescan_etc:
+            msg += ' %d%% complete, estimated completion in ' % dupescan_pc
+            if dupescan_etc < 90:
+                msg += '%d seconds' % dupescan_etc
+            elif dupescan_etc < (90*60):
+                msg += '%d minutes' % (dupescan_etc / 60)
+            else:
+                msg += '%d hours' % (dupescan_etc / 60 / 60)
+        Exception.__init__(self, msg)
+
diff --git a/casemgr/personmerge.py b/casemgr/personmerge.py
new file mode 100644
index 0000000..703e24b
--- /dev/null
+++ b/casemgr/personmerge.py
@@ -0,0 +1,335 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, utils
+from casemgr import globals, demogfields, person, fuzzyperson, persondupe, \
+                    syndrome
+
+class PersonMergeError(globals.Error): pass
+class PersonHasChanged(PersonMergeError): pass
+Error = PersonMergeError
+
+class MergeValue:
+    # A cut down version of a DemogField - to substitute our own field name
+    def __init__(self, merge, field):
+        self.merge = merge
+        self.name = field.name
+        self.render = getattr(field, 'render_case', None) or field.render
+        self.field = 'personmerge.person.' + field.name
+        self.disabled = False
+
+    def optionexpr(self):
+        return self.merge.demogfield(self.name).optionexpr()
+
+    def format(self):
+        return self.merge.demogfield(self.name).format()
+
+    def age_if_dob(self, ns):
+        return self.merge.demogfield(self.name).age_if_dob(ns)
+
+
+class MergeCol:
+    def __init__(self, merge, field, index):
+        self.merge = merge
+        self.name = field.name
+        self.label = field.label
+        self.field = MergeValue(merge, field)
+        value_a = getattr(merge.person_a, self.name)
+        value_b = getattr(merge.person_b, self.name)
+        self.show_a = bool(value_a)
+        self.show_b = bool(value_b)
+        self.conflict = (value_a and value_b and value_a != value_b)
+        if self.name == 'data_src':
+            self.source = 'd'
+        elif not value_a and not value_b:
+            self.source = 'd'
+        elif value_b and not value_a:
+            self.source = 'b'
+        else:
+            self.source = 'a'
+        self.source_field = 'personmerge.fields[%d].source' % index
+
+    def outtrans(self, ns):
+        return self.merge.demogfield(self.name).outtrans(ns) or ''
+
+    def desc_edit(self):
+        value_a = getattr(self.merge.person_a, self.name)
+        value_b = getattr(self.merge.person_b, self.name)
+        value_e = getattr(self.merge.person, self.name, None)
+        if value_e:
+            op = 'Edit'
+            hilite = value_e != value_a or value_e != value_b
+            ns = self.merge.person
+        elif self.source == 'a' and value_a != value_b:
+            op = 'A'
+            hilite = True
+            ns = self.merge.person_a
+        elif self.source == 'b' and value_a != value_b:
+            op = 'B'
+            hilite = True
+            ns = self.merge.person_b
+        elif self.source == 'd' and (value_a or value_b):
+            op = 'DELETE'
+            hilite = True
+            if value_a:
+                ns = self.merge.person_a
+            else:
+                ns = self.merge.person_b
+        else:
+            return None
+        return self.label, op, self.outtrans(ns), hilite
+
+    def apply(self, person_a, person_b):
+        value_a = getattr(person_a, self.name)
+        value_b = getattr(person_b, self.name)
+        initial_a = getattr(self.merge.person_a, self.name)
+        initial_b = getattr(self.merge.person_b, self.name)
+        if initial_a != value_a or initial_b != value_b:
+            raise PersonHasChanged
+        value_e = getattr(self.merge.person, self.name)
+        if value_e:
+            value = value_e
+        elif self.source == 'a':
+            value = value_a
+        elif self.source == 'b':
+            value = value_b
+        elif self.source == 'd':
+            value = None
+        setattr(person_a, self.name, value)
+        setattr(person_b, self.name, value)
+        return value_a != value, value_b != value
+
+
+class DOBMergeCol(MergeCol):
+
+    def __init__(self, merge, field, index):
+        MergeCol.__init__(self, merge, field, index)
+        a_exact = merge.person_a.DOB and not merge.person_a.DOB_prec
+        b_exact = merge.person_b.DOB and not merge.person_b.DOB_prec
+        if a_exact and not b_exact:
+            self.source = 'a'
+        elif b_exact and not a_exact:
+            self.source = 'b'
+
+    def apply(self, person_a, person_b):
+        def ne(a, b):
+            return a.DOB != b.DOB or a.DOB_prec != b.DOB_prec
+        def set(dst, src):
+            dst.DOB, dst.DOB_prec = src.DOB, src.DOB_prec
+        class NullDOB:
+            DOB = None
+            DOB_prec = None
+        if (ne(self.merge.person_a, person_a) 
+            or ne(self.merge.person_b, person_b)):
+            raise PersonHasChanged
+        if self.merge.person.DOB:
+            src = self.merge.person
+        elif self.source == 'a':
+            src = person_a
+        elif self.source == 'b':
+            src = person_b
+        elif self.source == 'd':
+            src = NullDOB
+        changed = ne(person_a, src), ne(person_b, src)
+        set(person_a, src)
+        set(person_b, src)
+        return changed
+
+class PMCaseDesc:
+    def __init__(self, case_id, syndrome_id, deleted):
+        self.case_id = case_id
+        self.syndrome_id = syndrome_id
+        self.syndrome = syndrome.syndromes[syndrome_id].name
+        self.deleted = deleted
+        self.style = ''
+        if self.deleted:
+            self.style = 'gray'
+
+class Merge:
+    def __init__(self, person_id_a, person_id_b):
+        self.id_a = person_id_a
+        self.id_b = person_id_b
+        self._demogfields = None
+        self.person = person.person()
+        self.init_fields(*self._fetch_persons())
+
+    def __getstate__(self):
+        attrs = dict(vars(self))
+        attrs['_demogfields'] = None
+        return attrs
+
+    def _fetch_persons(self, for_update=False):
+        query = globals.db.query('persons', for_update=for_update)
+        query.where_in('person_id', (self.id_a, self.id_b))
+        try:
+            x, y = query.fetchall()
+        except ValueError:
+            raise Error('Error fetching persons (incorrect number of rows)')
+        if x.person_id == self.id_a and y.person_id == self.id_b:
+            return x, y
+        elif x.person_id == self.id_b and y.person_id == self.id_a:
+            return y, x
+        else:
+            raise Error('Error fetching persons (wrong rows)')
+
+    def demogfield(self, name):
+        if self._demogfields is None:
+            self._demogfields = {}
+            for field in demogfields.get_demog_fields(globals.db, None):
+                self._demogfields[field.name] = field
+        return self._demogfields[name]
+
+    def init_fields(self, person_a, person_b):
+        self.person_a = person_a
+        self.person_b = person_b
+        self.fields = []
+        for field in demogfields.get_demog_fields(globals.db, None):
+            if hasattr(person_a, field.name) and hasattr(person_b, field.name):
+                if field.name == 'DOB':
+                    mc = DOBMergeCol(self, field, len(self.fields))
+                else:
+                    mc = MergeCol(self, field, len(self.fields))
+                self.fields.append(mc)
+        self.status, self.exclude_reason =\
+            persondupe.get_status(self.id_a, self.id_b, for_update=True)
+
+    def normalise(self):
+        self.person.normalise()
+
+    def desc_edit(self):
+        edits = []
+        for mc in self.fields:
+            desc = mc.desc_edit()
+            if desc:
+                edits.append(desc)
+        return edits
+
+    def exclude(self):
+        """
+        This pair has been falsely identified as a duplicate - record
+        the fact.
+        """
+        persondupe.exclude(self.id_a, self.id_b, self.exclude_reason)
+        globals.db.commit()
+        self.status = persondupe.STATUS_EXCLUDED
+
+    def include(self):
+        persondupe.clear_exclude(self.id_a, self.id_b)
+        globals.db.commit()
+        self.status = persondupe.STATUS_NEW
+
+    def cases(self):
+        query = globals.db.query('cases', order_by='case_id')
+        query.where_in('person_id', (self.id_a, self.id_b))
+        return query.fetchcols('case_id')
+
+    def desc_cases(self):
+        query = globals.db.query('cases', order_by='case_id')
+        query.where_in('person_id', (self.id_a, self.id_b))
+        cases = { False: [], True: [] }
+        for case_id, deleted in query.fetchcols(('case_id', 'deleted')):
+            cases[bool(deleted)].append(case_id)
+        ids = ['%s' % case_id for case_id in cases[False]] + \
+                ['(%s)' % case_id for case_id in cases[True]]
+        return utils.commalist(ids, 'and')
+
+    def desc_person_cases(self):
+        def fmt(person_id):
+            ids = ['%s' % case_id for case_id in cases[(person_id, False)]] + \
+                  ['(%s)' % case_id for case_id in cases[(person_id, True)]]
+            return utils.commalist(ids, 'and')
+        query = globals.db.query('cases')
+        query.where_in('person_id', (self.id_a, self.id_b))
+        a_cases, b_cases = [], []
+        cols = 'person_id', 'case_id', 'syndrome_id', 'deleted'
+        for person_id, case_id, syndrome_id, deleted in query.fetchcols(cols):
+            case_desc = PMCaseDesc(case_id, syndrome_id, deleted)
+            if person_id == self.id_a:
+                a_cases.append(case_desc)
+            elif person_id == self.id_b:
+                b_cases.append(case_desc)
+        return a_cases, b_cases
+
+    def merge(self, credentials):
+        # lock the relevent cases
+        persondupe.dupe_lock(globals.db)
+        query = globals.db.query('cases', for_update=True)
+        query.where_in('person_id', (self.id_a, self.id_b))
+        case_ids = query.fetchcols('case_id')
+        # lock the person records and make sure they haven't changed
+        # and estimate how how many changes each record might require
+        a_delta_count = b_delta_count = 0
+        person_a, person_b = self._fetch_persons(for_update=True)
+        for mc in self.fields:
+            try:
+                a_changed, b_changed = mc.apply(person_a, person_b)
+            except PersonHasChanged:
+                person_a.db_revert()
+                person_b.db_revert()
+                self.init_fields(person_a, person_b)
+                raise
+            if a_changed:
+                a_delta_count += 1
+            if b_changed:
+                b_delta_count += 1
+        # Now decide which direction to merge
+        if b_delta_count > a_delta_count:
+            update_person, delete_person = person_a, person_b
+        else:
+            update_person, delete_person = person_b, person_a
+        # Update the log
+        case_ids.sort()
+        update_desc = update_person.db_desc()
+        delete_desc = delete_person.db_desc()
+        if not update_desc:
+            update_desc = 'no edits required'
+        if not delete_desc:
+            delete_desc = 'no edits required'
+        desc = 'Merge person, System IDs %s, UPDATED %s, DELETED %s' %\
+                    (utils.commalist(case_ids, 'and'), update_desc, delete_desc)
+        for case_id in case_ids:
+            credentials.user_log(globals.db, desc, case_id=case_id)
+        # Now update the cases and persons
+        curs = globals.db.cursor()
+        try:
+            dbobj.execute(curs, 'UPDATE cases SET person_id=%s'
+                                ' WHERE person_id=%s',
+                          (update_person.person_id, delete_person.person_id))
+        finally:
+            curs.close()
+        fuzzyperson.update(globals.db, update_person.person_id,
+                           update_person.surname, update_person.given_names)
+        update_person.db_update(refetch=False)
+        delete_person.db_delete()
+#        globals.db.rollback()  # when testing
+        return update_person, case_ids
+
+
+def merge_case_persons(case_id_a, case_id_b):
+    query = globals.db.query('cases')
+    query.where_in('case_id', (case_id_a, case_id_b))
+    rows = dict(query.fetchcols(('case_id', 'person_id')))
+    person_id_a = rows.get(case_id_a)
+    person_id_b = rows.get(case_id_b)
+    if person_id_a is None:
+        raise Error('ID %s not found' % case_id_a)
+    if person_id_b is None:
+        raise Error('ID %s not found' % case_id_b)
+    if person_id_a == person_id_b:
+        raise Error('Both IDs map to the same person')
+    persondupe.dupe_lock(globals.db)
+    return Merge(person_id_a, person_id_b)
diff --git a/casemgr/phonetic_encode.py b/casemgr/phonetic_encode.py
new file mode 100644
index 0000000..9407781
--- /dev/null
+++ b/casemgr/phonetic_encode.py
@@ -0,0 +1,1297 @@
+# =============================================================================
+# encode.py - Routines for Soundex, NYSIIS and Double-Metaphone string
+#             encodings.
+#
+# Freely extensible biomedical record linkage (Febrl) Version 0.2.2
+# See http://datamining.anu.edu.au/projects/linkage.html
+#
+# =============================================================================
+# AUSTRALIAN NATIONAL UNIVERSITY OPEN SOURCE LICENSE (ANUOS LICENSE)
+# VERSION 1.1
+#
+# The contents of this file are subject to the ANUOS License Version 1.1 (the
+# "License"); you may not use this file except in compliance with the License.
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
+# the specific language governing rights and limitations under the License.
+# The Original Software is "encode.py".
+# The Initial Developers of the Original Software are Dr Peter Christen
+# (Department of Computer Science, Australian National University) and Dr Tim
+# Churches (Centre for Epidemiology and Research, New South Wales Department
+# of Health). Copyright (C) 2002, 2003 the Australian National University and
+# others. All Rights Reserved.
+# Contributors:
+#
+# =============================================================================
+
+"""Module encode.py - Routines for Soundex, Phonex, NYSIIS and Double-Metaphone
+                      string encodings.
+
+   Provides routines for different string encodings (as well as for encodings
+   of reversed names).
+
+   ROUTINES
+     soundex      Soundex
+     mod_soundex  Modified Soundex
+     phonex       Phonex
+     nysiis       NYSIIS
+     dmetaphone   Double-Metaphone
+
+   See doc strings of individual routines for detailed documentation.
+
+   Note that all encoding routines assume the input string only contains
+   letters and whitespaces, , but not digits or other ASCII characters.
+
+   If called from command line, a test routine is run which prints example
+   encodings for various strings.
+"""
+
+verbose = False
+
+# =============================================================================
+# Imports go here
+
+import string
+
+# =============================================================================
+
+def soundex(s, maxlen=4):
+  """Compute the soundex code for a string.
+
+  USAGE:
+    code = soundex(s, maxlen):
+
+  ARGUMENTS:
+    s        A string containing a name.
+    maxlen   Maximal length of the returned code. If a code is longer than
+             'maxlen' it is truncated. Default value is 4.
+
+  DESCRIPTION:
+    For more information on Soundex see:
+    - http://www.bluepoof.com/Soundex/info.html
+    - http://www.nist.gov/dads/HTML/soundex.html
+  """
+
+  # Translation table and characters that will not be used for soundex  - - - -
+  #
+  transtable = string.maketrans('abcdefghijklmnopqrstuvwxyz', \
+                                '01230120022455012623010202')
+  # deletechars='aeiouhwy '
+  deletechars = ' '
+
+  if (not s):
+    return maxlen*'0'  # Or 'z000' for compatibility with other implementations
+
+  s2 = string.translate(s[1:],transtable,deletechars)
+
+  s3 = s[0]  # Keep first character of original string
+
+  # Only add numbers if they are not the same as the previous number
+  for i in s2:
+    if (i != s3[-1]):
+      s3 = s3+i
+
+  # Remove all '0'
+  s4 = s3.replace('0', '')
+
+  # Fill up with '0' to maxlen length
+  #
+  s4 = s4+maxlen*'0'
+
+  # A log message for high volume log output (level 3) - - - - - - - - - - - -
+  #
+  if verbose:
+    print '3:  Soundex encoding for string: "%s"' % (s)
+    print '3:    Code: %s' % (s4[:maxlen])
+
+  return s4[:maxlen]  # Return first maxlen characters
+
+# =============================================================================
+
+def mod_soundex(s, maxlen=4):
+  """Compute the modified soundex code for a string.
+
+  USAGE:
+    code = mod_soundex(s, maxlen):
+
+  ARGUMENTS:
+    s        A string containing a name.
+    maxlen   Maximal length of the returned code. If a code is longer than
+             'maxlen' it is truncated. Default value is 4.
+
+  DESCRIPTION:
+    For more information on the modified Soundex see:
+    - http://www.bluepoof.com/Soundex/info2.html
+  """
+
+  # Translation table and characters that will not be used for soundex  - - - -
+  #
+  transtable = string.maketrans('abcdefghijklmnopqrstuvwxyz', \
+                                '01360240043788015936020505')
+  deletechars='aeiouhwy '
+
+  if (not s):
+    return maxlen*'0'  # Or 'z000' for compatibility with other implementations
+
+  s2 = string.translate(s[1:],transtable, deletechars)
+
+  s3 = s[0]  # Keep first character of original string
+
+  # Only add numbers if they are not the same as the previous number
+  for i in s2:
+    if (i != s3[-1]):
+      s3 = s3+i
+
+  # Fill up with '0' to maxlen length
+  #
+  s4 = s3+maxlen*'0'
+
+  # A log message for high volume log output (level 3) - - - - - - - - - - - -
+  #
+  if verbose:
+    print '3:  Mod Soundex encoding for string: "%s"' % (s)
+    print '3:    Code: %s' % (s4[:maxlen])
+
+  return s4[:maxlen]
+
+# =============================================================================
+
+def phonex(s, maxlen=4):
+  """Compute the phonex code for a string.
+
+  USAGE:
+    code = phonex(s, maxlen):
+
+  ARGUMENTS:
+    s        A string containing a name.
+    maxlen   Maximal length of the returned code. If a code is longer than
+             'maxlen' it is truncated. Default value is 4.
+
+  DESCRIPTION:
+    Based on the algorithm as described in:
+    "An Assessment of Name Matching Algorithms, A.J. Lait and B. Randell,
+     Technical Report number 550, Department of Computing Science,
+     University of Newcastle upon Tyne, 1996"
+
+    Available at: 
+      http://www.cs.ncl.ac.uk/~brian.randell/home.informal/
+             Genealogy/NameMatching.pdf
+
+    Bug-fixes regarding 'h','ss','hss' etc. strings thanks to Marion Sturtevant
+  """
+
+  if (not s):
+    return maxlen*'0'  # Or 'z000' for compatibility with other implementations
+
+  # Preprocess input string - - - - - - - - - - - - - - - - - - - - - - - - - -
+  #
+  while (s and s[-1] == 's'):  # Remove all 's' at the end
+    s = s[:-1]
+
+  if (not s):
+    return maxlen*'0'
+
+  if (s[:2] == 'kn'):  # Remove 'k' from beginning if followed by 'n'
+    s = s[1:]
+  elif (s[:2] == 'ph'):  # Replace 'ph' at beginning with 'f'
+    s = 'f'+s[2:]
+  elif (s[:2] == 'wr'):  # Remove 'w' from beginning if followed by 'r'
+    s = s[1:]
+
+  if (s[0] == 'h'):  # Remove 'h' from beginning
+    s = s[1:]
+
+  if (not s):
+    return maxlen*'0'
+
+  # Make phonetic equivalence of first character
+  #
+  if (s[0] in 'eiouy'):
+    s = 'a'+s[1:]
+  elif (s[0] == 'p'):
+    s = 'b'+s[1:]
+  elif (s[0] == 'v'):
+    s = 'f'+s[1:]
+  if (s[0] in 'kq'):
+    s = 'c'+s[1:]
+  elif (s[0] == 'j'):
+    s = 'g'+s[1:]
+  elif (s[0] == 'z'):
+    s = 's'+s[1:]
+
+  # Modified soundex coding - - - - - - - - - - - - - - - - - - - - - - - - - -
+  #
+
+  s_len = len(s)
+  code = ''  # Phonex code
+  i = 0
+
+  while (i < s_len):  # Loop over all characters in s
+    s_i = s[i]
+    code_i = '0'  # Default code
+
+    if (s_i in 'bfpv'):
+      code_i = '1'
+
+    elif (s_i in 'cskgjqxz'):
+      code_i = '2'
+
+    elif (s_i in 'dt') and (i < s_len-1) and (s[i+1] != 'c'):
+      code_i = '3'
+
+    elif (s_i == 'l') and ((i == s_len-1) or \
+                           ((i < s_len-1) and (s[i+1] in 'aeiouy'))):
+      code_i = '4'
+
+    elif (s_i in 'mn'):
+      code_i = '5'
+      if (i < s_len-1) and (s[i+1] in 'dg'):
+        s = s[:i+1]+s_i+s[i+2:]  # Replace following D or G with current M or N
+
+    elif (s_i == 'r') and ((i == s_len-1) or \
+                           ((i < s_len-1) and (s[i+1] in 'aeiouy'))):
+      code_i = '6'
+
+    if (i == 0):  # Handle beginning of string
+      last = code_i
+      code += s_i  # Start code with a letter
+
+    else:
+      if (code_i != last) and (code_i != '0'):
+
+        # If the code differs from previous code and it's not a vowel code
+        #
+        code += code_i
+
+      last = code[-1]
+    i += 1
+
+  # Fill up with '0' to maxlen length
+  #
+  code += maxlen*'0'
+
+  # A log message for high volume log output (level 3) - - - - - - - - - - - -
+  #
+  if verbose:
+    print '3:  Phonex encoding for string: "%s"' % (s)
+    print '3:    Code: %s' % (code[:maxlen])
+
+  return code[:maxlen]  # Return first maxlen characters
+
+# =============================================================================
+
+def nysiis(s, maxlen=4):
+  """Compute the NYSIIS code for a string.
+
+  USAGE:
+    code = nysiis(s, maxlen):
+
+  ARGUMENTS:
+    s        A string containing a name.
+    maxlen   Maximal length of the returned code. If a code is longer than
+             'maxlen' it is truncated. Default value is 4.
+
+  DESCRIPTION:
+    For more information on NYSIIS see:
+    - http://www.dropby.com/indexLF.html?content=/NYSIIS.html
+    - http://www.nist.gov/dads/HTML/nysiis.html
+  """
+
+  if (not s):
+    return ''
+
+  # Remove trailing S or Z
+  #
+  while s and s[-1] in 'sz':
+    s = s[:-1]
+
+  # Translate first characters of string  
+  #
+  if (s[:3] == 'mac'):  # Initial 'MAC' -> 'MC'
+    s = 'mc'+s[3:]
+  elif (s[:2] == 'pf'):  # Initial 'PF' -> 'F'
+    s = s[1:]
+
+  # Translate some suffix characters:
+  #
+  suff_dict = {'ix':'ic', 'ex':'ec', 'ye':'y', 'ee':'y', 'ie':'y', \
+               'dt':'d', 'rt':'d', 'rd':'d', 'nt':'n', 'nd':'n'}
+  suff = s[-2:]
+  s = s[:-2]+suff_dict.get(suff, suff)  # Replace suffix if in dictionary
+
+  # Replace EV with EF
+  #
+  if (s[2:].find('ev') > -1):
+    s = s[:-2]+s[2:].replace('ev','ef')
+
+  if (not s):
+    return ''
+
+  first = s[0]  # Save first letter for final code
+
+  # Replace all vowels with A and delete whitespaces
+  #
+  voweltable = string.maketrans('eiou', 'aaaa')
+  s2 = string.translate(s,voweltable, ' ')
+
+  if (not s2):  # String only contained whitespaces
+    return ''
+
+  # Remove all W that follow an A
+  #
+  s2 = s2.replace('aw','a')
+
+  # Various replacement patterns
+  #
+  s2 = s2.replace('ght','gt')
+  s2 = s2.replace('dg','g')
+  s2 = s2.replace('ph','f')
+  s2 = s2[0]+s2[1:].replace('ah','a')
+  s3 = s2[0]+s2[1:].replace('ha','a')
+  s3 = s3.replace('kn','n')
+  s3 = s3.replace('k','c')
+  s4 = s3[0]+s3[1:].replace('m','n')
+  s5 = s4[0]+s4[1:].replace('q','g')
+  s5 = s5.replace('sh','s')
+  s5 = s5.replace('sch','s')
+  s5 = s5.replace('yw','y')
+  s5 = s5.replace('wr','r')
+
+  # If not first or last, replace Y with A  
+  #
+  s6 = s5[0]+s5[1:-1].replace('y','a')+s5[-1]
+
+  # If not first character, replace Z with S
+  #
+  s7 = s6[0]+s6[1:].replace('z','s')
+
+  # Replace trailing AY with Y
+  #
+  if (s7[-2:] == 'ay'):
+    s7 = s7[:-2]+'y'
+
+  # Remove trailing vowels (now only A)
+  #
+  while s7 and s7[-1] == 'a':
+    s7 = s7[:-1]
+
+  if (len(s7) == 0):
+    resstr = ''
+  else:
+    resstr = s7[0]
+
+    # Only add letters if they differ from the previous letter
+    #
+    for i in s7[1:]:
+      if (i != resstr[-1]):
+        resstr=resstr+i
+
+  # Now compile final result string
+  #
+  if (first in 'aeiou'):
+    resstr = first+resstr[1:]
+
+  # A log message for high volume log output (level 3) - - - - - - - - - - - -
+  #
+  if verbose:
+    print '3:  NYSIIS encoding for string: "%s"' % (s)
+    print '3:    Code: %s' % (resstr[:maxlen])
+
+  return resstr[:maxlen]
+
+# =============================================================================
+
+def dmetaphone(s, maxlen=4):
+  """Compute the Double Metaphone code for a string.
+
+  USAGE:
+    code = dmetaphone(s, maxlen):
+
+  ARGUMENTS:
+    s        A string containing a name.
+    maxlen   Maximal length of the returned code. If a code is longer than
+             'maxlen' it is truncated. Default value is 4.
+
+  DESCRIPTION:
+    Based on:
+    - Lawrence Philips C++ code as published in C/C++ Users Journal (June 2000)
+      and available at:
+      http://www.cuj.com/articles/2000/0006/0006d/0006d.htm
+    - Perl/C implementation
+      http://www.cpan.org/modules/by-authors/id/MAURICE/
+    See also:
+    - http://aspell.sourceforge.net/metaphone/
+    - http://www.nist.gov/dads/HTML/doubleMetaphone.html
+  """
+
+  if (not s):
+    return ''
+
+  primary = ''
+  secondary = ''
+  alternate = ''
+  primary_len = 0
+  secondary_len = 0
+
+  # Sub routines  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+  #
+  def isvowel(c):
+    if (c in 'aeiouy'):
+      return 1
+    else:
+      return 0
+
+  def slavogermanic(str):
+    if (str.find('w')>-1) or (str.find('k')>-1) or (str.find('cz')>-1) or \
+       (str.find('witz')>-1):
+      return 1
+    else:
+      return 0
+
+  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+  length = len(s)
+  if (len < 1):
+    return ''
+  last = length-1
+
+  current = 0  # Current position in string
+  workstr = s+'      '
+
+  if (workstr[0:2] in ['gn','kn','pn','wr','ps']):
+    current = current+1  # Skip first character
+
+  if (workstr[0] == 'x'):  # Initial 'x' is pronounced like 's'
+    primary = primary+'s'
+    primary_len = primary_len+1
+    secondary = secondary+'s'
+    secondary_len = secondary_len+1
+    current = current+1
+
+  while (primary_len < maxlen) or (secondary_len < maxlen):
+    if (current >= length):
+      break
+
+    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+    # Main loop, analyse current character
+    #
+    c = workstr[current]
+
+    if (c in 'aeiouy'):
+      if (current == 0):  # All initial vowels map to 'a'
+        primary = primary+'a'
+        primary_len = primary_len+1
+        secondary = secondary+'a'
+        secondary_len = secondary_len+1
+      current=current+1
+
+    elif (c == 'b'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      primary = primary+'p'
+      primary_len = primary_len+1
+      secondary = secondary+'p'
+      secondary_len = secondary_len+1
+      if (workstr[current+1] == 'b'):
+        current=current+2
+      else:
+        current=current+1
+
+    # elif (s == 'c'):  # C
+    #    primary = primary+'s'
+    #    primary_len = primary_len+1
+    #    secondary = secondary+'s'
+    #    secondary_len = secondary_len+1
+    #    current = current+1
+
+    elif (c == 'c'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (current > 1) and (not isvowel(workstr[current-2])) and \
+         workstr[current-1:current+2] == 'ach' and \
+         (workstr[current+2] != 'i' and \
+         (workstr[current+2] != 'e' or \
+          workstr[current-2:current+4] in ['bacher','macher'])):
+        primary = primary+'k'  # Various germanic special cases
+        primary_len = primary_len+1
+        secondary = secondary+'k'
+        secondary_len = secondary_len+1
+        current = current+2
+      elif (current == 0) and (workstr[0:6] == 'caesar'):
+        primary = primary+'s'
+        primary_len = primary_len+1
+        secondary = secondary+'s'
+        secondary_len = secondary_len+1
+        current = current+2
+      elif (workstr[current:current+4] == 'chia'): # Italian 'chianti'
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'k'
+        secondary_len = secondary_len+1
+        current = current+2
+      elif (workstr[current:current+2] == 'ch'):
+        if (current > 0) and (workstr[current:current+4] == 'chae'):
+          primary = primary+'k'  # Find 'michael'
+          primary_len = primary_len+1
+          secondary = secondary+'x'
+          secondary_len = secondary_len+1
+          current = current+2
+        elif (current == 0) and \
+           (workstr[current+1:current+6] in ['harac','haris'] or \
+            workstr[current+1:current+4] in \
+              ['hor','hym','hia','hem']) and \
+           workstr[0:6] != 'chore':
+          primary = primary+'k'  # Greek roots, eg. 'chemistry'
+          primary_len = primary_len+1
+          secondary = secondary+'k'
+          secondary_len = secondary_len+1
+          current = current+2
+        elif (workstr[0:4] in ['van ','von '] or \
+              workstr[0:3] == 'sch') or \
+            workstr[current-2:current+4] in \
+              ['orches','archit','orchid'] or \
+            workstr[current+2] in ['t','s'] or \
+            ((workstr[current-1] in ['a','o','u','e'] or \
+              current==0) and \
+            workstr[current+2] in \
+              ['l','r','n','m','b','h','f','v','w',' ']):
+          primary = primary+'k'
+          primary_len = primary_len+1
+          secondary = secondary+'k'
+          secondary_len = secondary_len+1
+          current = current+2
+        else:
+          if (current > 0):
+            if (workstr[0:2] == 'mc'):
+              primary = primary+'k'
+              primary_len = primary_len+1
+              secondary = secondary+'k'
+              secondary_len = secondary_len+1
+              current = current+2
+            else:
+              primary = primary+'x'
+              primary_len = primary_len+1
+              secondary = secondary+'k'
+              secondary_len = secondary_len+1
+              current = current+2
+          else:
+            primary = primary+'x'
+            primary_len = primary_len+1
+            secondary = secondary+'x'
+            secondary_len = secondary_len+1
+            current=current+2
+      elif (workstr[current:current+2] == 'cz') and \
+         (workstr[current-2:current+2] != 'wicz'):
+        primary = primary+'s'
+        primary_len = primary_len+1
+        secondary = secondary+'x'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (workstr[current+1:current+4] == 'cia'):
+        primary = primary+'x'
+        primary_len = primary_len+1
+        secondary = secondary+'x'
+        secondary_len = secondary_len+1
+        current=current+3
+      elif (workstr[current:current+2] == 'cc') and \
+           not (current==1 and workstr[0] == 'm'):
+        if (workstr[current+2] in ['i','e','h']) and \
+           (workstr[current+2:current+4] != 'hu'):
+          if (current == 1 and workstr[0] == 'a') or \
+             (workstr[current-1:current+4] in ['uccee','ucces']):
+            primary = primary+'ks'
+            primary_len = primary_len+2
+            secondary = secondary+'ks'
+            secondary_len = secondary_len+2
+            current=current+3
+          else:
+            primary = primary+'x'
+            primary_len = primary_len+1
+            secondary = secondary+'x'
+            secondary_len = secondary_len+1
+            current=current+3
+        else:  # Pierce's rule
+          primary = primary+'k'
+          primary_len = primary_len+1
+          secondary = secondary+'k'
+          secondary_len = secondary_len+1
+          current=current+2
+      elif (workstr[current:current+2] in ['ck','cg','cq']):
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'k'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (workstr[current:current+2] in ['ci','ce','cy']):
+        if (workstr[current:current+3] in ['cio','cie','cia']):
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'x'
+          secondary_len = secondary_len+1
+          current=current+2
+        else:
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'s'
+          secondary_len = secondary_len+1
+          current=current+2
+      else:
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'k'
+        secondary_len = secondary_len+1
+        if (workstr[current+1:current+3] in [' c',' q',' g']):
+          current=current+3
+        else:
+          if (workstr[current+1] in ['c','k','q']) and \
+             (workstr[current+1:current+3] not in ['ce','ci']):
+            current=current+2
+          else:
+            current=current+1
+
+    elif (c == 'd'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current:current+2] == 'dg'):
+        if (workstr[current+2] in ['i','e','y']):  # Eg. 'edge'
+          primary = primary+'j'
+          primary_len = primary_len+1
+          secondary = secondary+'j'
+          secondary_len = secondary_len+1
+          current=current+3
+        else:  # Eg. 'edgar'
+          primary = primary+'tk'
+          primary_len = primary_len+2
+          secondary = secondary+'tk'
+          secondary_len = secondary_len+2
+          current=current+2
+      elif (workstr[current:current+2] in ['dt','dd']):
+        primary = primary+'t'
+        primary_len = primary_len+1
+        secondary = secondary+'t'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        primary = primary+'t'
+        primary_len = primary_len+1
+        secondary = secondary+'t'
+        secondary_len = secondary_len+1
+        current=current+1
+
+    elif (c == 'f'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'f'):
+        current=current+2
+      else:
+        current=current+1
+      primary = primary+'f'
+      primary_len = primary_len+1
+      secondary = secondary+'f'
+      secondary_len = secondary_len+1
+
+    elif (c == 'g'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'h'):
+        if (current > 0 and not isvowel(workstr[current-1])):
+          primary = primary+'k'
+          primary_len = primary_len+1
+          secondary = secondary+'k'
+          secondary_len = secondary_len+1
+          current=current+2
+        elif (current==0):
+          if (workstr[current+2] == 'i'): # Eg. ghislane, ghiradelli
+            primary = primary+'j'
+            primary_len = primary_len+1
+            secondary = secondary+'j'
+            secondary_len = secondary_len+1
+            current=current+2
+          else:
+            primary = primary+'k'
+            primary_len = primary_len+1
+            secondary = secondary+'k'
+            secondary_len = secondary_len+1
+            current=current+2
+        elif (current>1 and workstr[current-2] in ['b','h','d']) or \
+             (current>2 and workstr[current-3] in ['b','h','d']) or \
+             (current>3 and workstr[current-4] in ['b','h']):
+          current=current+2
+        else:
+          if (current > 2) and (workstr[current-1] == 'u') and \
+             (workstr[current-3] in ['c','g','l','r','t']):
+            primary = primary+'f'
+            primary_len = primary_len+1
+            secondary = secondary+'f'
+            secondary_len = secondary_len+1
+            current=current+2
+          else:
+            if (current > 0) and (workstr[current-1] != 'i'):
+              primary = primary+'k'
+              primary_len = primary_len+1
+              secondary = secondary+'k'
+              secondary_len = secondary_len+1
+              current=current+2
+            else:
+              current=current+2
+      elif (workstr[current+1] == 'n'):
+        if (current==1) and (isvowel(workstr[0])) and \
+           (not slavogermanic(workstr)):
+          primary = primary+'kn'
+          primary_len = primary_len+2
+          secondary = secondary+'n'
+          secondary_len = secondary_len+1
+          current=current+2
+        else:
+          if (workstr[current+2:current+4] != 'ey') and \
+             (workstr[current+1] != 'y') and \
+             (not slavogermanic(workstr)):
+            primary = primary+'n'
+            primary_len = primary_len+1
+            secondary = secondary+'kn'
+            secondary_len = secondary_len+2
+            current=current+2
+          else:
+            primary = primary+'kn'
+            primary_len = primary_len+2
+            secondary = secondary+'kn'
+            secondary_len = secondary_len+2
+            current=current+2
+      elif (workstr[current+1:current+3] == 'li') and \
+           (not slavogermanic(workstr)):
+        primary = primary+'kl'
+        primary_len = primary_len+2
+        secondary = secondary+'l'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (current==0) and ((workstr[current+1] == 'y') or \
+           (workstr[current+1:current+3] in \
+           ['es','ep','eb','el','ey','ib','il','in','ie','ei','er'])):
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'j'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (workstr[current+1:current+3] == 'er' or \
+           workstr[current+1] == 'y') and \
+           workstr[0:6] not in ['danger','ranger','manger'] and \
+           workstr[current-1] not in ['e','i'] and \
+           workstr[current-1:current+2] not in ['rgy','ogy']:
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'j'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (workstr[current+1] in ['e','i','y']) or \
+           (workstr[current-1:current+3] in ['aggi','oggi']):
+        if (workstr[0:4] in ['van ','von ']) or \
+           (workstr[0:3] == 'sch') or \
+           (workstr[current+1:current+3] == 'et'):
+          primary = primary+'k'
+          primary_len = primary_len+1
+          secondary = secondary+'k'
+          secondary_len = secondary_len+1
+          current=current+2
+        else:
+          if (workstr[current+1:current+5] == 'ier '):
+            primary = primary+'j'
+            primary_len = primary_len+1
+            secondary = secondary+'j'
+            secondary_len = secondary_len+1
+            current=current+2
+          else:
+            primary = primary+'j'
+            primary_len = primary_len+1
+            secondary = secondary+'k'
+            secondary_len = secondary_len+1
+            current=current+2
+      else:
+        if (workstr[current+1] == 'g'):
+          current=current+2
+        else:
+          current=current+1
+        primary = primary+'k'
+        primary_len = primary_len+1
+        secondary = secondary+'k'
+        secondary_len = secondary_len+1
+
+    elif (c =='h'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (current == 0 or isvowel(workstr[current-1])) and \
+         isvowel(workstr[current+1]):
+        primary = primary+'h'
+        primary_len = primary_len+1
+        secondary = secondary+'h'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        current=current+1
+
+    elif (c =='j'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current:current+4] == 'jose') or \
+         (workstr[0:4] == 'san '):
+        if (current == 0 and workstr[4] == ' ') or \
+           (workstr[0:4] == 'san '):
+          primary = primary+'h'
+          primary_len = primary_len+1
+          secondary = secondary+'h'
+          secondary_len = secondary_len+1
+          current=current+1
+        else:
+          primary = primary+'j'
+          primary_len = primary_len+1
+          secondary = secondary+'h'
+          secondary_len = secondary_len+1
+          current=current+1
+      elif (current==0) and (workstr[0:4] != 'jose'):
+        primary = primary+'j'
+        primary_len = primary_len+1
+        secondary = secondary+'a'
+        secondary_len = secondary_len+1
+        if (workstr[current+1] == 'j'):
+          current=current+2
+        else:
+          current=current+1
+      else:
+        if (isvowel(workstr[current-1])) and \
+           (not slavogermanic(workstr)) and \
+           (workstr[current+1] in ['a','o']):
+          primary = primary+'j'
+          primary_len = primary_len+1
+          secondary = secondary+'h'
+          secondary_len = secondary_len+1
+        else:
+          if (current == last):
+            primary = primary+'j'
+            primary_len = primary_len+1
+            #secondary = secondary+''
+            #secondary_len = secondary_len+0
+          else:
+            if (workstr[current+1] not in \
+               ['l','t','k','s','n','m','b','z']) and \
+               (workstr[current-1] not in ['s','k','l']):
+              primary = primary+'j'
+              primary_len = primary_len+1
+              secondary = secondary+'j'
+              secondary_len = secondary_len+1
+        if (workstr[current+1] == 'j'):
+          current=current+2
+        else:
+          current=current+1
+
+    elif (c =='k'):  #  - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'k'):
+        current=current+2
+      else:
+        current=current+1
+      primary = primary+'k'
+      primary_len = primary_len+1
+      secondary = secondary+'k'
+      secondary_len = secondary_len+1
+
+    elif (c == 'l'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'l'):
+        if (current == (length-3)) and \
+           (workstr[current-1:current+3] in ['illo','illa','alle']) or \
+           ((workstr[last-1:last+1] in ['as','os']  or
+           workstr[last] in ['a','o']) and \
+           workstr[current-1:current+3] == 'alle'):
+          primary = primary+'l'
+          primary_len = primary_len+1
+          #secondary = secondary+''
+          #secondary_len = secondary_len+0
+          current=current+2
+        else:
+          primary = primary+'l'
+          primary_len = primary_len+1
+          secondary = secondary+'l'
+          secondary_len = secondary_len+1
+          current=current+2
+      else:
+        primary = primary+'l'
+        primary_len = primary_len+1
+        secondary = secondary+'l'
+        secondary_len = secondary_len+1
+        current=current+1
+
+    elif (c == 'm'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current-1:current+2] == 'umb' and \
+         ((current+1) == last or \
+          workstr[current+2:current+4] == 'er')) or \
+         workstr[current+1] == 'm':
+        current=current+2
+      else:
+        current=current+1
+      primary = primary+'m'
+      primary_len = primary_len+1
+      secondary = secondary+'m'
+      secondary_len = secondary_len+1
+
+    elif (c == 'n'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'n'):
+        current=current+2
+      else:
+        current=current+1
+      primary = primary+'n'
+      primary_len = primary_len+1
+      secondary = secondary+'n'
+      secondary_len = secondary_len+1
+
+    elif (c == 'p'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'h'):
+        primary = primary+'f'
+        primary_len = primary_len+1
+        secondary = secondary+'f'
+        secondary_len = secondary_len+1
+        current=current+2
+      elif (workstr[current+1] in ['p','b']):
+        primary = primary+'p'
+        primary_len = primary_len+1
+        secondary = secondary+'p'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        primary = primary+'p'
+        primary_len = primary_len+1
+        secondary = secondary+'p'
+        secondary_len = secondary_len+1
+        current=current+1
+
+    elif (c == 'q'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'q'):
+        current=current+2
+      else:
+        current=current+1
+      primary = primary+'k'
+      primary_len = primary_len+1
+      secondary = secondary+'k'
+      secondary_len = secondary_len+1
+
+    elif (c == 'r'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (current==last) and (not slavogermanic(workstr)) and \
+         (workstr[current-2:current] == 'ie') and \
+         (workstr[current-4:current-2] not in ['me','ma']):
+        # primary = primary+''
+        # primary_len = primary_len+0
+        secondary = secondary+'r'
+        secondary_len = secondary_len+1
+      else:
+        primary = primary+'r'
+        primary_len = primary_len+1
+        secondary = secondary+'r'
+        secondary_len = secondary_len+1
+      if (workstr[current+1] == 'r'):
+        current=current+2
+      else:
+        current=current+1
+
+    elif (c == 's'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current-1:current+2] in ['isl','ysl']):
+        current=current+1
+      elif (current==0) and (workstr[0:5] == 'sugar'):
+        primary = primary+'x'
+        primary_len = primary_len+1
+        secondary = secondary+'s'
+        secondary_len = secondary_len+1
+        current=current+1
+      elif (workstr[current:current+2] == 'sh'):
+        if (workstr[current+1:current+5] in \
+           ['heim','hoek','holm','holz']):
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'s'
+          secondary_len = secondary_len+1
+          current=current+2
+        else:
+          primary = primary+'x'
+          primary_len = primary_len+1
+          secondary = secondary+'x'
+          secondary_len = secondary_len+1
+          current=current+2
+      elif (workstr[current:current+3] in ['sio','sia']) or \
+           (workstr[current:current+4] == 'sian'):
+        if (not slavogermanic(workstr)):
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'x'
+          secondary_len = secondary_len+1
+          current=current+3
+        else:
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'s'
+          secondary_len = secondary_len+1
+          current=current+3
+      elif ((current==0) and (workstr[1] in ['m','n','l','w'])) or \
+           (workstr[current+1] == 'z'):
+        primary = primary+'s'
+        primary_len = primary_len+1
+        secondary = secondary+'x'
+        secondary_len = secondary_len+1
+        if (workstr[current+1] == 'z'):
+          current=current+2
+        else:
+          current=current+1
+      elif (workstr[current:current+2] == 'sc'):
+        if (workstr[current+2] == 'h'):
+          if (workstr[current+3:current+5] in \
+             ['oo','er','en','uy','ed','em']):
+            if (workstr[current+3:current+5] in ['er','en']):
+              primary = primary+'x'
+              primary_len = primary_len+1
+              secondary = secondary+'sk'
+              secondary_len = secondary_len+2
+              current=current+3
+            else:
+              primary = primary+'sk'
+              primary_len = primary_len+2
+              secondary = secondary+'sk'
+              secondary_len = secondary_len+2
+              current=current+3
+          else:
+            if (current==0) and (not isvowel(workstr[3])) and \
+               (workstr[3] != 'w'):
+              primary = primary+'x'
+              primary_len = primary_len+1
+              secondary = secondary+'s'
+              secondary_len = secondary_len+1
+              current=current+3
+            else:
+              primary = primary+'x'
+              primary_len = primary_len+1
+              secondary = secondary+'x'
+              secondary_len = secondary_len+1
+              current=current+3
+        elif (workstr[current+2] in ['i','e','y']):
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'s'
+          secondary_len = secondary_len+1
+          current=current+3
+        else:
+          primary = primary+'sk'
+          primary_len = primary_len+2
+          secondary = secondary+'sk'
+          secondary_len = secondary_len+2
+          current=current+3
+      elif (current==last) and \
+           (workstr[current-2:current] in ['ai','oi']):
+        # primary = primary+''
+        # primary_len = primary_len+0
+        secondary = secondary+'s'
+        secondary_len = secondary_len+1
+        if (workstr[current+1] in ['s','z']):
+          current=current+2
+        else:
+          current=current+1
+      else:
+        primary = primary+'s'
+        primary_len = primary_len+1
+        secondary = secondary+'s'
+        secondary_len = secondary_len+1
+        if (workstr[current+1] in ['s','z']):
+          current=current+2
+        else:
+          current=current+1
+
+    elif (c == 't'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current:current+4] == 'tion'):
+        primary = primary+'x'
+        primary_len = primary_len+1
+        secondary = secondary+'x'
+        secondary_len = secondary_len+1
+        current=current+3
+      elif (workstr[current:current+3] in ['tia','tch']):
+        primary = primary+'x'
+        primary_len = primary_len+1
+        secondary = secondary+'x'
+        secondary_len = secondary_len+1
+        current=current+3
+      elif (workstr[current:current+2] == 'th') or \
+           (workstr[current:current+3] == 'tth'):
+        if (workstr[current+2:current+4] in ['om','am']) or \
+           (workstr[0:4] in ['von ','van ']) or (workstr[0:3] == 'sch'):
+          primary = primary+'t'
+          primary_len = primary_len+1
+          secondary = secondary+'t'
+          secondary_len = secondary_len+1
+          current=current+2
+        else:
+          primary = primary+'0'
+          primary_len = primary_len+1
+          secondary = secondary+'t'
+          secondary_len = secondary_len+1
+          current=current+2
+      elif (workstr[current+1] in ['t','d']):
+        primary = primary+'t'
+        primary_len = primary_len+1
+        secondary = secondary+'t'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        primary = primary+'t'
+        primary_len = primary_len+1
+        secondary = secondary+'t'
+        secondary_len = secondary_len+1
+        current=current+1
+
+    elif (c == 'v'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'v'):
+        current=current+2
+      else:
+        current=current+1 
+      primary = primary+'f'
+      primary_len = primary_len+1
+      secondary = secondary+'f'
+      secondary_len = secondary_len+1
+
+    elif (c == 'w'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current:current+2] == 'wr'):
+        primary = primary+'r'
+        primary_len = primary_len+1
+        secondary = secondary+'r'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        if (current==0) and (isvowel(workstr[1]) or \
+           workstr[0:2] == 'wh'):
+          if (isvowel(workstr[current+1])):
+            primary = primary+'a'
+            primary_len = primary_len+1
+            secondary = secondary+'f'
+            secondary_len = secondary_len+1
+            #current=current+1
+          else:
+            primary = primary+'a'
+            primary_len = primary_len+1
+            secondary = secondary+'a'
+            secondary_len = secondary_len+1
+            #current=current+1
+        if (current==last and isvowel(workstr[current-1])) or \
+           workstr[current-1:current+4] in \
+           ['ewski','ewsky','owski','owsky'] or \
+           workstr[0:3] == 'sch':
+          # primary = primary+''
+          # primary_len = primary_len+0
+          secondary = secondary+'f'
+          secondary_len = secondary_len+1
+          current=current+1
+        elif (workstr[current:current+4] in ['witz','wicz']):
+          primary = primary+'ts'
+          primary_len = primary_len+2
+          secondary = secondary+'fx'
+          secondary_len = secondary_len+2
+          current=current+4
+        else:
+          current=current+1
+
+    elif (c == 'x'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if not (current==last and \
+         (workstr[current-3:current] in ['iau','eau'] or \
+          workstr[current-2:current] in ['au','ou'])):
+        primary = primary+'ks'
+        primary_len = primary_len+2
+        secondary = secondary+'ks'
+        secondary_len = secondary_len+2
+      if (workstr[current+1] in ['c','x']):
+        current=current+2
+      else:
+        current=current+1
+
+    elif (c == 'z'):  # - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      if (workstr[current+1] == 'h'):
+        primary = primary+'j'
+        primary_len = primary_len+1
+        secondary = secondary+'j'
+        secondary_len = secondary_len+1
+        current=current+2
+      else:
+        if (workstr[current+1:current+3] in ['zo','zi','za']) or \
+           (slavogermanic(workstr) and \
+           (current > 0 and workstr[current-1] != 't')):
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'ts'
+          secondary_len = secondary_len+2
+          if (workstr[current+1] == 'z'):
+            current=current+2
+          else:
+            current=current+1
+        else:
+          primary = primary+'s'
+          primary_len = primary_len+1
+          secondary = secondary+'s'
+          secondary_len = secondary_len+1
+          if (workstr[current+1] == 'z'):
+            current=current+2
+          else:
+            current=current+1
+
+    else:   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+      current=current+1
+
+    # End main loop
+
+  if (primary == secondary):
+    # If both codes are the same set the second's length to 0 so it's not used
+    secondary_len = 0
+
+  # if (secondary_len > 0):
+  #   return [primary[:maxlen], secondary[:maxlen]]
+  # else:
+  #   return [primary[:maxlen]]
+
+  # A log message for high volume log output (level 3) - - - - - - - - - - - -
+  #
+  if verbose:
+    print '3:  Double Metaphone encoding for string: "%s"' %(s)
+    print '3:    Code: %s, secondary code: %s' % \
+            (primary[:maxlen], secondary[:maxlen])
+
+  return primary[:maxlen]  # Only return primary encoding
+
+# =============================================================================
+
+#
+# Do some tests if called from command line
+#
+
+if (__name__ == '__main__'):
+
+  print 'Febrl module "encode.py"'
+  print '------------------------'
+  print
+
+  print 'Original names:'
+  print '            Name     Phonex   Soundex  ModSoundex      NYSIIS  ',
+  print '  D-Metaphone'
+  print '---------------------------------------------------------------'+ \
+        '--------------'
+
+  namelist = ['peter','christen','ole','nielsen','markus','hegland',\
+              'stephen','steve','roberts','tim','churches','xiong',\
+              'ng','miller','millar','foccachio','van de hooch', \
+              'xiao ching','asawakun','prapasri','von der felde','vest',
+              'west','oioi','ohio','oihcca', 'nielsen', 'kim', 'lim', \
+              'computer','record','linkage','probabilistic']
+
+  for n in namelist:
+    soundex_my     = soundex(n)
+    soundex_mod_my = mod_soundex(n)
+    phonex_my     = phonex(n)
+    nysiis_my     = nysiis(n)
+    dmeta_my      = dmetaphone(n)
+
+    print '%16s %10s %9s %11s %11s %15s'% \
+          (n, phonex_my, soundex_my, soundex_mod_my, nysiis_my, dmeta_my)
+
+  print
+  print 'Reversed names:'
+  print '            Name     Phonex   Soundex  ModSoundex      NYSIIS  ',
+  print '  D-Metaphone'
+  print '---------------------------------------------------------------'+ \
+        '--------------'
+
+  for n in namelist:
+    rn = list(n)
+    rn.reverse()
+    rn = ''.join(rn)
+    soundex_my     = soundex(rn)
+    soundex_mod_my = mod_soundex(rn)
+    phonex_my     = phonex(rn)
+    nysiis_my     = nysiis(rn)
+    dmeta_my      = dmetaphone(rn)
+
+    print '%16s %10s %9s %11s %11s %15s'% \
+          (rn, phonex_my, soundex_my, soundex_mod_my, nysiis_my, dmeta_my)
+
+# =============================================================================
diff --git a/casemgr/preferences.py b/casemgr/preferences.py
new file mode 100644
index 0000000..4e74b94
--- /dev/null
+++ b/casemgr/preferences.py
@@ -0,0 +1,145 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard Python Libs
+import sys
+import time
+import cPickle
+
+# Application modules
+import config
+from cocklebur import dbobj, datetime
+
+
+class Preferences:
+    # Batch fetch user prefs on creation, save changed prefs on command.
+    commit_delay = 10
+    debug = False
+    n_recent = 16
+
+    defaults = {
+        'bulletin_time': None,
+        'contact_viz': None,
+        'current_unit': None,
+        'date_style': config.date_style,
+        'font_size': '10pt',
+        'jscalendar': True,
+        'nobble_back_button': config.nobble_back_button,
+        'persons_per_page': 10,
+        'persons_order': None,
+        'phonetic_search': True,
+        'results_per_page': 25,
+        'ts_params': None,
+        'recent_cases': None,
+    }
+
+    def __init__(self, user_id, preferences):
+        self.user_id = user_id
+        self.need_commit = False
+        self.commit_time = time.time()
+        self.preferences = {}
+        self.lost_reason = None
+        if preferences:
+            try:
+                self.preferences = cPickle.loads(str(preferences))
+            except Exception, e:
+                self.lost_reason = str(e)
+        self.apply()
+
+    def reset_all(self):
+        self.preferences = {}
+        self.need_commit = True
+
+    def reset(self, name):
+        try:
+            del self.preferences[name]
+            self.need_commit = True
+        except KeyError:
+            pass
+
+    def set(self, name, value):
+        old_value = self.preferences.get(name)
+        if self.debug:
+            print >> sys.stderr, 'PREFS set %s, changed %s, old %r, value %r' %\
+                (name, old_value != value, old_value, value)
+        if old_value != value:
+            if name == 'date_style':
+                datetime.set_date_style(value)
+            if value == self.defaults.get(name):
+                self.reset(name)
+            else:
+                self.preferences[name] = value
+            self.need_commit = True
+
+    def set_from_str(self, name, value):
+        default_type = type(self.defaults[name])
+        if default_type is bool:
+            value = (value == 'True')
+        else:
+            value = default_type(value)
+        self.set(name, value)
+
+    def get(self, name, default=None):
+        if default is None:
+            default = self.defaults.get(name)
+        return self.preferences.get(name, default)
+
+    def set_recent_case(self, case_id, label):
+        recent_cases = self.preferences.get('recent_cases')
+        if recent_cases is None:
+            recent_cases = [(case_id, label)]
+        else:
+            recent_cases = [(i, l) for i, l in recent_cases if i != case_id]
+            del recent_cases[self.n_recent-1:]
+            recent_cases.insert(0, (case_id, label))
+        self.preferences['recent_cases'] = recent_cases
+        self.need_commit = True
+
+    def get_recent_cases(self):
+        return list(self.preferences.get('recent_cases', []))
+
+    def apply(self):
+        # Some preferences effect libraries
+        datetime.set_date_style(self.get('date_style'))
+
+    def commit(self, db, immediate=False):
+        self.apply()
+        now = time.time()
+        if self.debug:
+            print >> sys.stderr, "PREFS commit, immediate %s, need_commit %s, when %s" % (immediate, self.need_commit, (now - (self.commit_time + self.commit_delay)))
+        if (self.need_commit and 
+            (immediate or self.commit_time + self.commit_delay < now)):
+            if self.preferences:
+                data = dbobj.Binary(cPickle.dumps(self.preferences))
+            else:
+                data = None
+            try:
+                curs = db.cursor()
+                try:
+                    dbobj.execute(curs, 'UPDATE users SET preferences=%s'
+                                        ' WHERE user_id = %s',
+                                        (data, self.user_id))
+                finally:
+                    curs.close()
+            except dbobj.DatabaseError:
+                db.rollback()
+                raise
+            else:
+                db.commit()
+                self.commit_time = now
+                self.need_commit = False
diff --git a/casemgr/printforms.py b/casemgr/printforms.py
new file mode 100644
index 0000000..28613c5
--- /dev/null
+++ b/casemgr/printforms.py
@@ -0,0 +1,114 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import form_ui
+from casemgr import globals, demogfields, syndrome
+
+class UIError(globals.Error): pass
+
+class DemographicsForm:
+    def __init__(self, syndrome_id):
+        self.label = '_demographics'
+        self.syndrome_id = syndrome_id
+        self.name = 'Demographics/Identification'
+        self.version = None
+
+    def get_form_ui(self):
+        synd = syndrome.syndromes[self.syndrome_id]
+        f = form_ui.Form('%s %s' % (synd.name, self.name), table='None')
+        f.question(text=synd.description, inputs=[])
+        demog_fields = demogfields.get_demog_fields(globals.db, 
+                                                    self.syndrome_id)
+        for field in demog_fields.context_fields('case'):
+            if field.render in ('textinput', 'case_dob'):
+                f.question(text=field.label, 
+                    input=form_ui.TextInput(field.name))
+            elif field.render == 'textarea':
+                f.question(text=field.label,
+                    input=form_ui.TextArea(field.name))
+            elif field.render == 'dateinput':
+                f.question(text=field.label, 
+                    input=form_ui.TextInput(field.name, post_text='dd-mm-yyyy'))
+            elif field.render == 'datetimeinput':
+                f.question(text=field.label, 
+                    input=form_ui.TextInput(field.name, 
+                                post_text='dd-mm-yyyy HH:MM'))
+            elif field.render == 'select':
+                f.question(text=field.label, 
+                           input=form_ui.RadioList(field.name, 
+                                                   choices=field.optionexpr()))
+        f.update_labels()
+        return f
+
+
+class LoadForm:
+    def __init__(self, label, name, version):
+        self.label = label
+        self.name = name
+        self.version = version
+
+    def get_form_ui(self):
+        return globals.formlib.load(self.label, self.version)
+
+
+class Forms:
+    def __init__(self, syndrome_id):
+        self.clear()
+        self.loaded_syndrome_id = None
+        self.loaded_form_type = None
+        self.syndrome_id = syndrome_id
+        self.forms = None
+        self.refresh()
+
+    def refresh(self):
+        if self.loaded_syndrome_id != self.syndrome_id:
+            query = globals.db.query('forms', order_by='syndrome_forms_id')
+            query.join('RIGHT JOIN syndrome_forms'
+                       ' ON forms.label = syndrome_forms.form_label')
+            query.where('syndrome_id = %s', self.syndrome_id)
+            rows = query.fetchcols(('label', 'name', 'cur_version'))
+            self.forms = [LoadForm(*r) for r in rows]
+            self.forms.insert(0, DemographicsForm(self.syndrome_id))
+            self.loaded_syndrome_id = self.syndrome_id
+
+    def select_all(self):
+        self.include_forms = [f.label for f in self.forms]
+
+    def clear(self):
+        self.include_forms = []
+
+    def selected_forms(self):
+        forms = []
+        for form in self.forms:
+            if form.label in self.include_forms:
+                forms.append(form)
+        return forms
+
+    def check_forms(self):
+        if not self.include_forms:
+            raise UIError('Please select one or more forms')
+        all_okay = True
+        for form in self.forms:
+            if form.label in self.include_forms:
+                try:
+                    form.get_form_ui()
+                except FormError:
+                    self.include_forms.remove(form.label)
+                    all_okay = False
+        if not all_okay:
+            raise UIError('Some forms contain errors and have been disabled')
diff --git a/casemgr/pwcrypt.py b/casemgr/pwcrypt.py
new file mode 100644
index 0000000..d2a28ac
--- /dev/null
+++ b/casemgr/pwcrypt.py
@@ -0,0 +1,116 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import re
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import new as sha
+import binascii
+
+SALT_LEN = 8
+
+RANDOM_DEV = '/dev/urandom'
+
+PWCHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./'
+N_PWCHARS = len(PWCHARS)
+
+def poor_salt():
+    salt = ''.join([random.choice(PWCHARS) for i in range(SALT_LEN)])
+    return '$S$%s$' % salt
+
+
+def good_salt():
+    fd = os.open(RANDOM_DEV, os.O_RDONLY)
+    try:
+        data = os.read(fd, 16)
+    finally:
+        os.close(fd)
+    salt = ''.join([PWCHARS[ord(data[i]) % N_PWCHARS]
+                    for i in range(SALT_LEN)])
+    return '$S$%s$' % salt
+
+
+if os.path.exists(RANDOM_DEV):
+    salt = good_salt
+else:
+    salt = poor_salt
+
+
+def crypt(password, salt):
+    # We do this ourselves, because platform crypt() implementations vary too
+    # much, and we'd like our data to be portable.
+    fields = salt.split('$', 3)
+    try:
+        method, salt = fields[1:3]
+        if method != 'S':
+            raise ValueError
+    except ValueError:
+        raise ValueError('Invalid password format')
+    hash = binascii.b2a_base64(sha(salt + password).digest())[:-1]
+    return '$S$%s$%s' % (salt, hash)
+
+
+def flawed_pwd_check(crypt_pwd, pwd):
+    """
+    Old, flawed password checking scheme - two-character salt not saved
+    with password, so check requires a search of salt space. This makes a hash
+    collision 2704 times more likely.
+    """
+    import string
+    import md5
+    for letter1 in string.letters:
+         for letter2 in string.letters:
+              if crypt_pwd == md5.new(letter1+letter2+pwd).hexdigest():
+                   return True
+    return False
+
+
+def need_upgrade(crypt_pwd):
+    return '$' not in crypt_pwd
+
+
+def pwd_check(crypt_pwd, pwd):
+    if need_upgrade(crypt_pwd):
+        return flawed_pwd_check(crypt_pwd, pwd)
+    return crypt(pwd, crypt_pwd) == crypt_pwd
+
+
+def new_crypt(pwd):
+    return crypt(pwd, salt())
+
+class Error(Exception): pass
+
+bad_pwd_msg = (
+    'Passwords be at least 8 characters long, and must contain a mix of upper '
+    'and lower case letters and digits. They may also contain punctuation. '
+    'Upper case letters must not only be at the beginning of the password and'
+    'digits must not only be at the end of the password.'
+)
+
+def strong_pwd(pwd):
+    if not pwd:
+        raise Error(bad_pwd_msg)
+    upper = re.sub('[^A-Z]', '', pwd)
+    lower = re.sub('[^a-z]', '', pwd)
+    digits = re.sub('[^0-9]', '', pwd)
+    nonleadingupper = re.sub('[^A-Z]', '', pwd[1:])
+    nontrailingdigits = re.sub('[^0-9]', '', pwd[:-1])
+    if len(pwd) < 8 or not lower or not upper or not digits or not (nonleadingupper or nontrailingdigits):
+        raise Error(bad_pwd_msg)
diff --git a/casemgr/reports/__init__.py b/casemgr/reports/__init__.py
new file mode 100644
index 0000000..df241be
--- /dev/null
+++ b/casemgr/reports/__init__.py
@@ -0,0 +1,30 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+# Define the "public" interface
+from casemgr.reports.common import ReportParamError, ReportParseError, \
+                                   Error, sharing_tags
+from casemgr.reports.store import load, load_last, parse_file, delete, \
+                                  reports_cache, ReportMenu
+from casemgr.reports.contactvis import have_graphviz
+from casemgr.reports.report import new_report, \
+                                   report_types, \
+                                   type_label, \
+                                   report_type_optionexpr, \
+                                   LineReportParams
diff --git a/casemgr/reports/common.py b/casemgr/reports/common.py
new file mode 100644
index 0000000..049e2c8
--- /dev/null
+++ b/casemgr/reports/common.py
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals
+
+sharing_tags = ['last', 'private', 'unit', 'public', 'quick']
+
+class Error(globals.Error): pass
+class ReportParamError(Error): pass
+class ReportParseError(Error): pass
+class ReportLoadError(Error): pass
+
+class ImageReport:
+
+    render = 'image'
+
+    def __init__(self, filename):
+        self.imagefile = filename
+
+
+def boolstr(value):
+    return str(value).lower() in ('true', 'yes')
diff --git a/casemgr/reports/contactvis.py b/casemgr/reports/contactvis.py
new file mode 100644
index 0000000..50c3243
--- /dev/null
+++ b/casemgr/reports/contactvis.py
@@ -0,0 +1,317 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+import sys
+import os
+import signal
+import errno
+import tempfile
+
+from cocklebur import trafficlight
+from cocklebur.exepath import exepath
+from casemgr import globals, caseaccess, syndrome
+from casemgr.reports.common import *
+
+import config
+
+vismodes = [
+    ('fdp', 'Radial (fdp)'),
+    ('neato', 'Radial (neato)'),
+    ('twopi', 'Radial (twopi)'),
+    ('circo', 'Circular (circo)'),
+    ('dot', 'Hierarchial (dot)'),
+]
+okay_modes = set([name for name, label in vismodes])
+
+MAX_NODES = 600
+MAX_VERTICES = 1000
+
+
+class Error(globals.Error): pass
+
+_have_graphviz = None
+def have_graphviz():
+    global _have_graphviz
+    if _have_graphviz is None:
+        _have_graphviz = bool(exepath('dot') or exepath('twopi'))
+    return _have_graphviz
+
+
+TIMEOUT = 20
+
+def run_graphviz(dot, vismode, outputtype, outputdir=None, filename=None):
+    path = None
+    if vismode in okay_modes:
+        path = exepath(vismode)
+    if not path:
+        raise Error('Visualisation tool %r not found' % vismode)
+    if filename:
+        tw = os.open(filename, os.O_CREAT|os.O_WRONLY, 0666)
+        fn = filename
+    else:
+        tw, fn = tempfile.mkstemp('.' + outputtype, 'vis', outputdir)
+    pr, pw = os.pipe()
+    pid = os.fork()
+    if pid:
+        # Parent
+        os.close(pr)
+        os.write(pw, dot)
+        os.close(pw)
+        signal.signal(signal.SIGALRM, lambda s, f: None)
+        signal.alarm(TIMEOUT)
+        try:
+            wpid, status = os.waitpid(pid, 0)
+        except OSError, (eno, estr):
+            if eno == errno.EINTR:
+                os.kill(pid, signal.SIGKILL)
+                raise Error('Graph generation took long than %s seconds - '
+                            'reduce the number of cases or try another '
+                            'visualisation mode - hierarchial (dot) may work' %
+                            TIMEOUT)
+            raise
+        if status == 0:
+            return os.path.basename(fn)
+        raise Error('Graphviz %r failed' % path)
+    else:
+        os.close(pw)
+        os.dup2(pr, 0)
+        os.dup2(tw, 1)
+        os.close(pr)
+        os.close(tw)
+        try:
+            os.execl(path, vismode, '-T' + outputtype)
+        except OSError, (eno, estr):
+            sys.exit('%s: %s' % (path, estr))
+
+
+class Person:
+
+    def __init__(self, *args):
+        self.case_ids = set()
+        self.statuses = set()
+        self.label = ''
+
+
+class Collect:
+
+    def __init__(self, db, cred, person_cols,
+                 syndrome_id=None, case_ids=None):
+        self.relations = set()
+        self.case_persons = {}
+        self.persons = {}
+        self.short_labels = not person_cols
+        self.to_syndcs = {}
+        self.syndcs_label = {}
+        self.syndcs_color = {}
+        used_syndcs = set()
+        query = db.query('syndrome_case_status')
+        cols = 'syndcs_id', 'syndrome_id', 'name', 'label'
+        for syndcs_id, syndrome_id, name, label in query.fetchcols(cols):
+            self.to_syndcs[(syndrome_id, name)] = syndcs_id
+            self.syndcs_label[syndcs_id] = label
+        query = db.query('cases')
+        caseaccess.acl_query(query, cred)
+        query.join('JOIN case_contacts USING (case_id)')
+        if case_ids is not None:
+            subquery = query.sub_expr('OR')
+            subquery.where_in('case_id', case_ids)
+            subquery.where_in('contact_id', case_ids)
+        elif syndrome_id is not None:
+            query.where('syndrome_id = %s', syndrome_id)
+        cols = 'syndrome_id','person_id','case_id','contact_id','case_status'
+        self.persons = {}
+        for syndrome_id, person_id, case_id, contact_id, status in query.fetchcols(cols):
+            try:
+                person = self.persons[person_id]
+            except KeyError:
+                person = self.persons[person_id] = Person()
+            person.case_ids.add(case_id)
+            syndcs = self.to_syndcs.get((syndrome_id, status))
+            if syndcs is not None:
+                person.statuses.add(syndcs)
+                used_syndcs.add(syndcs)
+            self.case_persons[case_id] = person_id
+            if case_id < contact_id:
+                self.relations.add((case_id, contact_id))
+            else:
+                self.relations.add((contact_id, case_id))
+        if len(self.persons) > MAX_NODES:
+            raise Error('Sorry, this facility only supports < %d %s (%d required)' % (MAX_NODES, config.person_label, len(self.persons)))
+        if len(self.relations) > MAX_VERTICES:
+            raise Error('Sorry, this facility only supports < %d relations (%d required)' % (MAX_VERTICES, len(self.relations)))
+        if self.persons:
+            query = db.query('persons')
+            query.where_in('person_id', self.persons.keys())
+            for row in query.fetchcols(['person_id'] + list(person_cols)):
+                person = self.persons[row[0]]
+                person.label = '\\n'.join(map(str, row[1:]))
+        used_syndcs = list(used_syndcs)
+        used_syndcs.sort()
+        colors = trafficlight.make_n_colors(len(used_syndcs))
+        for color, syndcs in zip(colors, used_syndcs):
+            self.syndcs_color[syndcs] = color
+        self.syndcs_color[None] = '#dddddd';
+
+    def generate_dot(self, vismode, title='Case %ss' % config.contact_label):
+        lines = []
+        lines.append('graph appname {')
+        lines.append('splines=true;')
+        lines.append('overlap=false;')
+        lines.append('node [shape=circle, style=filled];')
+        if self.short_labels:
+            lines.append('node [fontsize=8, height=.8, width=.8, fixedsize=1];')
+        else:
+            lines.append('node [fontsize=6, height=.8, width=.8, fixedsize=1];')
+        # Legend
+        # Only dot and fdp seem to support clustered subgraphs - twopi and
+        # neato just render the nodes in random places.
+        if vismode in ('dot', 'fdp'):
+            lines.append('subgraph cluster_0 {')
+            lines.append('style=filled;')
+            lines.append('label="Legend";')
+            lines.append('bgcolor="#eeeeee";')
+            for syndcs, color in self.syndcs_color.iteritems():
+                lines.append('node [fillcolor="%s"];' % color)
+                lines.append('  X%s [label="%s"];' % (syndcs, self.syndcs_label.get(syndcs, 'unknown')))
+            lines.append('}')
+        # Sort by status
+        person_by_status = {}
+        for person_id, person in self.persons.iteritems():
+            statuses = list(person.statuses)
+            if statuses:
+                if len(statuses) > 1:
+                    statuses.sort()
+                status = statuses[-1]
+            else:
+                status = None
+            try:
+                status_persons = person_by_status[status]
+            except KeyError:
+                status_persons = person_by_status[status] = []
+            status_persons.append((person_id, person))
+        # Nodes (persons)
+        for syndcs, status_persons in person_by_status.iteritems():
+            lines.append('node [fillcolor="%s"];' % self.syndcs_color[syndcs])
+            for person_id, person in status_persons:
+                case_ids = list(person.case_ids)
+                case_ids.sort()
+                if len(case_ids) > 2:
+                    case_ids = case_ids[:2] + ['...']
+                case_ids = ','.join([str(id) for id in case_ids])
+                lines.append('  %s [label="%s\\n%s"];' % (person_id, 
+                                                        case_ids,
+                                                        person.label))
+        # Edges (contact)
+        lines.append('edge [style=bold,weight=1]')
+        for case_id, contact_id in self.relations:
+            try:
+                person_a = self.case_persons[case_id]
+                person_b = self.case_persons[contact_id]
+            except KeyError:
+                continue
+            lines.append('  %s -- %s;' % (person_a, person_b))
+        lines.append('label="%s";' % title)
+        lines.append('}')
+        return '\n'.join(lines)
+
+
+class ContactVisParamsMixin:
+
+    show_contactvis = True
+
+    vismode = 'fdp'
+    outputtype = 'png'
+    labelwith = 'surname,given_names'
+
+    _vismodes = None
+    def vismode_options(self):
+        if self._vismodes is None:
+            # Cache these on the class
+            ContactVisParamsMixin._vismodes = [(name, label)
+                                               for name, label in vismodes
+                                               if exepath(name)]
+        return self._vismodes
+
+    def labelwith_options(self):
+        return [
+            ('surname,given_names', 'System ID, Surname, Given Names'),
+            ('', 'System ID'),
+            ('locality,state', 'System ID, Locality, State'),
+        ]
+
+    def outputtype_options(self):
+        return [
+            ('png', 'PNG'),
+            ('svg', 'SVG'),
+            ('pdf', 'PDF'),
+            ('ps', 'Postscript'),
+        ]
+
+    def get_prefs(self, credentials):
+        params = credentials.prefs.get('contact_viz')
+        if params and isinstance(params, dict):
+            # params was briefly a tuple, hence the isinstance check
+            self.__dict__.update(params)
+
+    def update_prefs(self, credentials):
+        params = dict(vismode=self.vismode, 
+                      outputtype=self.outputtype, 
+                      labelwith=self.labelwith)
+        credentials.prefs.set('contact_viz', params)
+
+    def _defaults(self, msgs):
+        if not have_graphviz():
+            msgs.msg('err', '%s not available on this system (GraphViz not '
+                            'installed?)' % self.type_label)
+
+    def _check(self, msgs):
+        okay_modes = set([name for name, label in self.vismode_options()])
+        if self.vismode not in okay_modes:
+            msgs.msg('err', 'Visualisation tool %r not available (GraphViz not '
+                            'installed?)' % self.vismode)
+
+    def report(self, creds, msgs, filename=None):
+        self.check(msgs)
+        if msgs.have_errors():
+            return
+        labelwith = ()
+        if self.labelwith:
+            labelwith = self.labelwith.split(',')
+        if not self.vismode:
+            self.vismode = self.vismode_options()[0][0]
+        case_ids = self.get_case_ids(creds)
+        data = Collect(globals.db, creds, labelwith, case_ids=case_ids)
+        dot_script = data.generate_dot(self.vismode, title=self.title(creds)) 
+        if filename:
+            outputtype = os.path.splitext(filename)[1][1:] or self.outputtype
+        else:
+            outputtype = self.outputtype
+        return ImageReport(run_graphviz(dot_script, self.vismode, 
+                                        outputtype, config.scratchdir,
+                                        filename=filename))
+
+    def _to_xml(self, xmlgen, curnode):
+        e = xmlgen.push('contactvis')
+        e.attr('format', self.outputtype)
+        e.attr('labelwith', self.labelwith)
+        e.attr('vismode', self.vismode)
+        xmlgen.pop()
diff --git a/casemgr/reports/crosstab.py b/casemgr/reports/crosstab.py
new file mode 100644
index 0000000..fe15a4d
--- /dev/null
+++ b/casemgr/reports/crosstab.py
@@ -0,0 +1,390 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import datetime
+from cocklebur.form_ui.inputbase import OneChoiceBase
+
+from casemgr import globals, syndrome, demogfields
+from casemgr.reports.common import *
+
+import config
+
+TOTAL = '!TOTAL!'
+HEAD = '!HEAD!'
+
+class Axis(object):
+
+    def __init__(self, table, col, label, options):
+        assert table
+        assert col
+        self.table = table
+        self.col = col
+        self.tabcol = '%s.%s' % (table, col)
+        self.label = label
+        self.options = list(options)
+
+    def filter(self, query, val):
+        if val is None:
+            query.where('%s is null' % self.tabcol)
+        else:
+            query.where('%s = %%s' % self.tabcol, val)
+
+
+class DemogAxis(Axis):
+
+    form_name = None
+
+
+class FormAxis(Axis):
+
+    def __init__(self, form_name, form_version, table, col, label, options):
+        Axis.__init__(self, table, col, label, options)
+        self.form_name = form_name
+        self.form_version = form_version
+
+
+class DummyAxis(Axis):
+    
+    def __init__(self):
+        self.table = None
+        self.form_name = None
+        self.col = None
+        self.tabcol = 'null'
+        self.label = None
+        self.options = [(TOTAL, None)]
+
+
+class Tally(dict):
+
+    def add(self, count, *key):
+        self[key] = self.get(key, 0) + count
+
+
+class CrossTabCount:
+
+    render = 'crosstab'
+
+    def __init__(self, params, syndrome_id, title, row, column, page,
+                 empty_rowsncols=True,
+                 empty_pages=True):
+        self.params = params
+        self.syndrome_id = syndrome_id
+        self.title = title
+        self.row = row
+        self.col = column
+        self.page = page
+        self.empty_rowsncols = empty_rowsncols
+        self.empty_pages = empty_pages
+        self.form_name = None
+        self.form_table = None
+        self.cols = []
+        for axis in (self.row, self.col, self.page):
+            if axis.form_name:
+                if self.form_name and self.form_name != axis.form_name:
+                    raise Error('Crosstab between different forms is not '
+                                'supported')
+                self.form_name = axis.form_name
+                self.form_table = axis.table
+            if axis.table:
+                self.cols.append(axis.tabcol)
+                axis.options.append((None, 'Missing'))
+                axis.options.append((TOTAL, 'TOTAL'))
+        self.date = datetime.now()
+
+    def get_query(self, **kwargs):
+        query = globals.db.query('cases', **kwargs)
+        query.join('JOIN persons USING (person_id)')
+        if self.form_name:
+            query.join('LEFT JOIN case_form_summary'
+                       ' ON (cases.case_id=case_form_summary.case_id'
+                       ' AND form_label=%s)', self.form_name)
+            query.join('LEFT JOIN %s USING (summary_id)' % self.form_table)
+            query.where('NOT case_form_summary.deleted')
+        query.where('NOT cases.deleted')
+        query.where('cases.syndrome_id = %s', self.syndrome_id)
+        self.params.filter_query(query)
+        return query
+
+    def run(self):
+        query = self.get_query(group_by=','.join(self.cols))
+        cols = ['count(*)'] + self.cols
+        data = query.fetchcols(cols)
+        tally = Tally()
+        if self.page.table:
+            for count, row_val, col_val, page_val in data:
+                tally.add(count, row_val, col_val, page_val)
+                tally.add(count, row_val, TOTAL, page_val)
+                tally.add(count, TOTAL, col_val, page_val)
+                tally.add(count, TOTAL, TOTAL, page_val)
+                tally.add(count, row_val, col_val, TOTAL)
+                tally.add(count, row_val, TOTAL, TOTAL)
+                tally.add(count, TOTAL, col_val, TOTAL)
+                tally.add(count, TOTAL, TOTAL, TOTAL)
+            if not self.empty_pages:
+                self.page.options = [(val, label)
+                                     for val, label in self.page.options
+                                     if tally.get((TOTAL, TOTAL, val))]
+        else:
+            for count, row_val, col_val in data:
+                tally.add(count, row_val, col_val, TOTAL)
+                tally.add(count, row_val, TOTAL, TOTAL)
+                tally.add(count, TOTAL, col_val, TOTAL)
+                tally.add(count, TOTAL, TOTAL, TOTAL)
+        if not self.empty_rowsncols:
+            self.row.options = [(val, label)
+                                for val, label in self.row.options
+                                if tally.get((val, TOTAL, TOTAL))]
+            self.col.options = [(val, label)
+                                for val, label in self.col.options
+                                if tally.get((TOTAL, val, TOTAL))]
+        self.tally = tally
+
+    def style(self, row, col):
+        style = []
+        if row == TOTAL:
+            style.append('t')
+        elif row == HEAD:
+            style.append('b')
+        if col == TOTAL:
+            style.append('l')
+        elif col == HEAD:
+            style.append('r')
+        return ' '.join(style)
+
+    def get_key_case_ids(self, *coords):
+        query = self.get_query(distinct=True)
+        for axis, index in zip((self.row, self.col, self.page), coords):
+            val = axis.options[int(index)][0]
+            if val != TOTAL:
+                axis.filter(query, val)
+        return query.fetchcols('cases.case_id')
+
+    def desc_key(self, *coords):
+        desc = []
+        for axis, index in zip((self.row, self.col, self.page), coords):
+            index = int(index)
+            if axis.options[index][0] != TOTAL:
+                desc.append('%s: %s' % (axis.label, axis.options[index][1]))
+        return ', '.join(desc)
+
+
+class CrosstabNoneAxisParamsMeths:
+
+    def col_options(self, form_name):
+        return []
+
+    def check(self, form_name, msgs):
+        return
+
+    def get_axis(self, form_name):
+        return None
+
+    col_options = staticmethod(col_options)
+    check = staticmethod(check)
+    get_axis = staticmethod(get_axis)
+
+
+class CrosstabDemogAxisParamsMeths:
+
+
+    def col_options(self, form_name):
+        excl = ('deleted', 'tags')
+        available_cols = []
+        for f in self.params.demog_fields('report'):
+            if f.name and f.optionexpr is not None and f.name not in excl:
+                available_cols.append((f.name, f.label))
+        return available_cols
+
+    def check(self, form_name, msgs):
+        if self.field:
+            try:
+                field = self.params.demog_fields().field_by_name(self.field)
+            except KeyError:
+                msgs.msg('err', '%r input no longer available' % self.field)
+                self.field = ''
+
+    def get_axis(self, form_name):
+        try:
+            field = self.params.demog_fields().field_by_name(self.field)
+        except KeyError:
+            raise Error('demographic field %r not found' % self.field)
+        return DemogAxis(field.table, field.name, 
+                            field.label, field.optionexpr())
+
+    col_options = staticmethod(col_options)
+    check = staticmethod(check)
+    get_axis = staticmethod(get_axis)
+
+
+class CrosstabFormAxisParamsMeths:
+
+    def col_options(self, form_name):
+        available_cols = []
+        form = self.params.form_info(form_name).load()
+        if form is not None:
+            for input in form.get_inputs():
+                if isinstance(input, OneChoiceBase):
+                    name = input.column.lower()
+                    available_cols.append((name, input.label or input.column))
+        return available_cols
+
+    def check(self, form_name, msgs):
+        if self.field:
+            form = self.params.form_info(form_name).load()
+            try:
+                input = form.columns.find_input(self.field)
+            except KeyError:
+                msgs.msg('err', '"%s" form "%s" input no longer available' % 
+                            (form.label, self.field))
+                self.field = ''
+                return
+
+    def get_axis(self, form_name):
+        form = self.params.form_info(form_name).load()
+        if form is None:
+            raise Error('Form %r not found' % form_name)
+        try:
+            input = form.columns.find_input(self.field)
+        except KeyError:
+            raise Error('field %r on form %r not found' % 
+                        (self.field, form_name))
+        return FormAxis(form.name, form.version, form.table,
+                        self.field, input.label, input.get_choices())
+
+    col_options = staticmethod(col_options)
+    check = staticmethod(check)
+    get_axis = staticmethod(get_axis)
+
+
+class CrosstabAxisParams:
+
+    meths = {
+        'none': CrosstabNoneAxisParamsMeths,
+        'demog': CrosstabDemogAxisParamsMeths,
+        'form': CrosstabFormAxisParamsMeths,
+    }
+
+    def __init__(self, params):
+        self.params = params
+        self.form_name = 'none:'
+        self.field = ''
+
+    def form_options(self):
+        available = []
+        available.append(('none:', 'None'))
+        available.append(('demog:', 'Demographic fields'))
+        for info in self.params.all_form_info():
+            available.append(('form:' + info.name, info.label))
+        return available
+
+    def show_fields(self):
+        return self.form_name != 'none:'
+
+    def col_options(self):
+        mode, form_name = self.form_name.split(':')
+        return self.meths[mode].col_options(self, form_name)
+
+    def get_axis(self):
+        mode, form_name = self.form_name.split(':')
+        return self.meths[mode].get_axis(self, form_name)
+
+    def check(self, msgs):
+        mode, form_name = self.form_name.split(':')
+        self.meths[mode].check(self, form_name, msgs)
+
+    def to_xml(self, xmlgen, name):
+        mode, form_name = self.form_name.split(':')
+        if mode != 'none':
+            e = xmlgen.push('axis')
+            e.attr('name', name)
+            e.attr('type', mode)
+            if mode == 'form':
+                e.attr('form', form_name)
+            e.attr('field', self.field)
+            xmlgen.pop()
+
+
+class CrosstabParams:
+
+    show_axes = True
+    empty_rowsncols = 'False'
+    empty_pages = 'False'
+
+    def init(self):
+        self.row = CrosstabAxisParams(self)
+        self.col = CrosstabAxisParams(self)
+        self.page = CrosstabAxisParams(self)
+
+    def set_axis(self, name, type, field='', form=''):
+        if name == 'row':
+            axis = self.row
+        elif name == 'column':
+            axis = self.col
+        elif name == 'page':
+            axis = self.page
+        else:
+            raise Error('Bad cross-tab axis name: %r' % name)
+        if type not in ('none', 'demog', 'form'):
+            raise Error('Bad cross-tab axis type: %r' % type)
+        axis.form_name = '%s:%s' % (type, form)
+        axis.field = field
+
+    def _check(self, msgs):
+        for axis in (self.row, self.col, self.page):
+            axis.check(msgs)
+
+    def report(self, cred, msgs):
+        self.check(msgs)
+        if msgs.have_errors():
+            return
+        row = self.row.get_axis()
+        if row is None:
+            raise Error('A row field must be selected')
+        col = self.col.get_axis()
+        if col is None:
+            raise Error('A column field must be selected')
+        page = self.page.get_axis()
+        if page is None:
+            page = DummyAxis()
+        report = CrossTabCount(self, self.syndrome_id, self.title(cred),
+                               row, col, page, 
+                               empty_rowsncols=(self.empty_rowsncols == 'True'),
+                               empty_pages=(self.empty_pages == 'True'))
+        report.run()
+        return report
+
+    def _forms_used(self, used):
+        for axis in (self.row, self.col, self.page):
+            type, name = axis.form_name.split(':')
+            if type == 'form':
+                used.add(name)
+
+    def _to_xml(self, xmlgen, curnode):
+        e = xmlgen.push('crosstab')
+        if boolstr(self.empty_rowsncols):
+            e.attr('include_empty_rowsncols', 'yes')
+        if boolstr(self.empty_pages):
+            e.attr('include_empty_pages', 'yes')
+        self.row.to_xml(xmlgen, 'row')
+        self.col.to_xml(xmlgen, 'column')
+        self.page.to_xml(xmlgen, 'page')
+        xmlgen.pop()
diff --git a/casemgr/reports/epicurve.py b/casemgr/reports/epicurve.py
new file mode 100644
index 0000000..551bf46
--- /dev/null
+++ b/casemgr/reports/epicurve.py
@@ -0,0 +1,608 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# NOTE - this module does not include logic for the case-contact visualisation.
+
+import sys, os
+import math
+import textwrap
+import itertools
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from mx.DateTime import DateTimeType, DateTimeDelta, RelativeDateTime, Monday
+
+from cocklebur import datetime, trafficlight, utils
+
+from casemgr import globals
+from casemgr.reports.common import *
+
+import config
+
+class Error(globals.Error): pass
+
+
+def import_pylab():
+    """
+    We defer loading matplotlib until it's actually needed as it can
+    take several seconds to import, and it may not be available at all.
+    """
+    global matplotlib, pylab
+    if 'matplotlib' not in sys.modules:
+        # Problem: matplotlib wants to load it's font cache (pickle) and rc
+        # files from a writable directory. This is potentially a security
+        # problem in a CGI app, but we have no solution at this time.
+        homedir = os.path.expanduser('~')
+        mpldir = os.path.join(homedir, '.matplotlib')
+        if not os.access(mpldir, os.W_OK) and not os.access(homedir, os.W_OK):
+            mpldir = os.path.join(config.scratchdir, '.matplotlib')
+        os.environ['MPLCONFIGDIR'] = mpldir
+        if not os.path.exists(mpldir):
+            os.mkdir(mpldir)
+        try:
+            import matplotlib
+            matplotlib.use('Agg')
+            import pylab
+        except ImportError, e:
+            raise Error('matplotlib not available (%s)?' % (e))
+
+def pad_axis(ax, axis, amount=0.01):
+    lower, upper = getattr(ax, 'get_%slim' % axis)()
+    abit = (upper - lower) * amount
+    getattr(ax, 'set_%slim' % axis)(lower - abit, upper + abit)
+
+def same_scale(axis, *axes):
+    lower = []
+    upper = []
+    for ax in axes:
+        l, u = getattr(ax, 'get_%slim' % axis)()
+        lower.append(l)
+        upper.append(u)
+    l = min(lower)
+    u = max(upper)
+    for ax in axes:
+        getattr(ax, 'set_%slim' % axis)(l, u)
+
+
+class CondCol:
+    def __init__(self, data, optionexpr=None):
+        self.data = map(str, data)
+        values = set(self.data)
+        self.order = []
+        self.labels = {}
+        self.order_labels = []
+        if optionexpr is not None:
+            for v, l in optionexpr:
+                if v in values:
+                    self.order.append(v)
+                    self.labels[v] = str(l)
+                    self.order_labels.append((v, str(l)))
+        missing = list(values - set(self.order))
+        missing.sort()
+        for v in missing:
+            self.order.append(v)
+            self.labels[v] = str(v)
+            self.order_labels.append((v, str(v)))
+
+
+class EpiCurve:
+    max_bins = 400              # Limit number of bars on chart
+    max_ticks = 40              # Approx limit on readable X tickmarks
+
+    def __init__(self, title=None):
+        import_pylab()
+        self.dates = None       # Event dates (list of DateTime)
+        self.date_label = None
+        self.lower_dates = None
+        self.lower_date_label = None
+        self.stack = None       # If stacking, a CondCol
+        self.first = None       # First date (DateTime)
+        self.last = None        # Last date (DateTime)
+        self.span = None        # Span of dates (DateTimeDelta)
+        self.n_bins = None      # Number of bins (int)
+        self.bin_span = None    # Span of each bin (DateTimeDelta)
+        self.title = title
+
+    def calc_span(self):
+        """
+        Find the first and last date, then round them down and up
+        (respecively).
+        """
+        assert self.dates
+        if self.lower_dates:
+            dates = filter(None, self.dates + self.lower_dates)
+        else:
+            dates = filter(None, self.dates)
+        if not dates:
+            raise Error('No date records found')
+        start_of_day = RelativeDateTime(hour=0, minute=0, second=0)
+        start_of_next_day = RelativeDateTime(days=1, hour=0, minute=0, second=0)
+        self.first = min(dates) + start_of_day
+        self.last = max(dates) + start_of_next_day
+        self.span = self.last - self.first
+        assert isinstance(self.first, DateTimeType)
+        assert isinstance(self.last, DateTimeType)
+
+    def set_dates(self, dates, date_label=None):
+        self.dates = dates
+        if date_label is not None:
+            self.date_label = date_label
+
+    def set_lower_dates(self, dates, date_label=None):
+        self.lower_dates = dates
+        if date_label is not None:
+            self.lower_date_label = date_label
+
+    def set_stack(self, data, optionexpr, 
+                  label=None, ratios=False, suppress=None):
+        assert len(self.dates) == len(data)
+        self.stack_label = label
+        self.stack = CondCol(data, optionexpr)
+        self.stack_ratios = ratios
+        self.stack_suppress = suppress
+
+    def calc_bin_span(self, days):
+        """
+        Given a desired bin span in days, calculate number of bins that
+        comes closest.
+        """
+        assert self.span
+        if (days % 7) == 0:
+            # If user asked for a bin width that is a multiple of 7 days, shift
+            # bins to align with weeks.
+            self.first += RelativeDateTime(weekday=(Monday,0))
+            self.span = self.last - self.first
+        self.bin_span = DateTimeDelta(days)
+        self.n_bins = int(math.ceil(self.span / self.bin_span))
+        if self.n_bins > self.max_bins:
+            raise Error('Too many bins (%d, maximum is %d) - make bins bigger, or limit date range' % (self.n_bins, self.max_bins))
+
+    def set_n_bins(self, n_bins):
+        """
+        Given a desired number of bins, calculate the closest possible number
+        of bins.
+        """
+        self.calc_span()
+        days = round((self.span / n_bins).days)
+        if days < 1:
+            days = 1
+        self.calc_bin_span(days)
+
+    def set_n_days(self, days):
+        self.calc_span()
+        self.calc_bin_span(days)
+
+    def calc_tick_labels(self):
+        """
+        Generate ticks & tick labels. If the density of the ticks looks like
+        it will be too high, we start skipping ticks (tick_stride).
+        """
+        assert self.n_bins
+        assert self.bin_span
+        tick_labels = []
+        ticks = []
+        tick_stride = int(math.ceil(self.n_bins / float(self.max_ticks)))
+        tick = self.first
+        if self.bin_span.days == 1:
+            adj = 0
+            if tick_stride == 1:
+                step = RelativeDateTime(days=1)
+            elif tick_stride <= 7:
+                step = RelativeDateTime(weeks=1, weekday=(Monday,0))
+                tick += step
+            elif tick_stride <= 14:
+                step = RelativeDateTime(weeks=2, weekday=(Monday,0))
+                tick += RelativeDateTime(weeks=1, weekday=(Monday,0))
+            else:
+                step = RelativeDateTime(day=1, months=1)
+                tick += step
+        else:
+            adj = -0.5
+            step = self.bin_span * tick_stride
+        while tick < self.last:
+            bin = (tick - self.first) / self.bin_span
+            ticks.append(bin+adj)
+            tick_labels.append(str(datetime.mx_parse_date(tick)))
+            tick += step
+        return ticks, tick_labels
+
+    def info(self, msgs):
+        n_recs = len(self.dates)
+        info = ['%d records' % n_recs]
+        date_missing = sum([not date for date in self.dates])
+        if date_missing:
+            info.append(', %d missing %s' % (date_missing, self.date_label))
+        if self.lower_dates:
+            lower_date_missing = sum([not date for date in self.lower_dates])
+            if lower_date_missing:
+                info.append(', %d missing %s' % (lower_date_missing, 
+                                                 self.lower_date_label))
+        else:
+            lower_date_missing = 0
+        if (date_missing == n_recs and 
+                (not self.lower_dates or lower_date_missing == n_recs)):
+            lvl = 'err'
+            info.insert(0, 'Error - ')
+        elif date_missing or lower_date_missing:
+            lvl = 'warn'
+        else:
+            lvl = 'info'
+        msgs.msg(lvl, ''.join(info))
+
+    def date_bin(self, dates):
+        """
+        Do the date binning
+        """
+        assert self.n_bins
+        assert self.bin_span
+        bins = pylab.zeros(self.n_bins)
+        for date in dates:
+            if date is not None:
+                bin = int((date - self.first) / self.bin_span)
+                bins[bin] += 1
+        return bins
+
+    def strata_date_bin(self, dates, *condcols):
+        """
+        Do date binning with strata.
+
+        Strata is a list of tuples. There must be a tuple for each date entry.
+        """
+        assert self.n_bins
+        assert self.bin_span
+        cols = [cc.data for cc in condcols]
+        rows = zip(*cols)
+        strata_bins = {}
+        for c in set(rows):
+            strata_bins[c] = pylab.zeros(self.n_bins)
+        for d, c in itertools.izip(dates, rows):
+            if d is not None:
+                bin = int((d - self.first) / self.bin_span)
+                strata_bins[c][bin] += 1
+        return strata_bins
+
+    def strata_ratios(self, strata_bins):
+        values, bins = zip(*strata_bins.iteritems())
+        matrix = pylab.array(bins, dtype='d')
+        ratios = matrix * 100.0 / pylab.add.reduce(matrix)
+        return dict(zip(values, ratios))
+
+    def graph(self, outfmt, filename=None):
+        def wrap(title):
+            if title:
+                return textwrap.fill(title, 30)
+        def _graph(ax, dates, legend=True):
+            ax.xaxis.set_ticks(ticks)
+            pylab.setp(ax.xaxis.get_ticklabels(), rotation=90, fontsize=8)
+            ax.xaxis.set_major_formatter(pylab.FixedFormatter(tick_labels))
+            ylocator = pylab.MaxNLocator(10, steps=[1,2,5,10],integer=1)
+            ax.yaxis.set_major_locator(ylocator)
+            ax.yaxis.grid(1)
+            if self.stack:
+                bottom = pylab.zeros(self.n_bins)
+                legend_handles = []
+                legend_labels = []
+                strata_bins = self.strata_date_bin(dates, self.stack)
+                if self.stack_ratios:
+                    strata_bins = self.strata_ratios(strata_bins)
+                for n, (v, l) in enumerate(self.stack.order_labels):
+                    if self.stack_suppress and v in self.stack_suppress:
+                        continue
+                    bins = strata_bins[(v,)]
+                    handles = pylab.bar(x, bins, bottom=bottom, 
+                                        width=1.0,
+                                        align='center', 
+                                        color=colors[n])
+                    legend_handles.append(handles[0])
+                    legend_labels.append(wrap(l))
+                    bottom += bins
+                if legend:
+                    l = pylab.legend(legend_handles, legend_labels, loc=0,
+                                     prop=dict(size=8),
+                                     title=wrap(self.stack_label))
+                    pylab.setp(l.get_title(), fontsize=9)
+#                    pylab.setp(l.get_texts(), fontsize=8)
+                if self.stack_ratios:
+                    ax.set_ylim(0, 100)
+            else:
+                bins = self.date_bin(dates)
+                pylab.bar(x, bins, width=1.0, color='#bbbbff', align='center')
+            pad_axis(ax, 'x')
+            pad_axis(ax, 'y')
+
+        ticks, tick_labels = self.calc_tick_labels()
+        x = pylab.arange(self.n_bins)
+        pylab.figure(figsize=(10,6), dpi=100, facecolor='w')
+        colors = None
+        ylabel = 'Count'
+        if self.stack:
+            colors = trafficlight.make_n_colors(len(self.stack.order))
+            if self.stack_ratios:
+                ylabel = 'Ratio'
+        if self.lower_dates:
+            lax = pylab.axes([0.1, 0.2, .8, 0.34], frameon=False)
+            _graph(lax, self.lower_dates, False)
+            pylab.ylabel(wrap(self.lower_date_label + ' ' + ylabel), fontsize=9)
+
+            ax = pylab.axes([0.1, 0.58, .8, 0.34], frameon=False)
+            _graph(ax, self.dates)
+            ax.xaxis.set_major_formatter(pylab.NullFormatter())
+            pylab.ylabel(wrap(self.date_label + ' ' + ylabel), fontsize=9)
+            same_scale('x', lax, ax)
+            #same_scale('y', lax, ax)
+        else:
+            ax = pylab.axes([0.1, 0.2, .8, 0.7], frameon=False)
+            _graph(ax, self.dates)
+            pylab.ylabel(ylabel, fontsize=9)
+            if self.date_label is not None:
+                pylab.xlabel(self.date_label)
+
+        if self.title:
+            pylab.title(self.title)
+        if not filename:
+            outfn = utils.randfn('vis', outfmt)
+            filename = os.path.join(config.scratchdir, outfn)
+        else:
+            outfn = filename
+        pylab.savefig(filename)
+        return outfn
+
+
+class FieldInfo(object):
+
+    def data(self):
+        return self.fields.get_column(self.index)
+
+
+class DemogFieldInfo(FieldInfo):
+
+    join = None
+
+    def __init__(self, field):
+        self.field = field
+        self.column = '%s.%s' % (field.table, field.name)
+        self.label = field.label
+
+    def options(self):
+        return self.field.optionexpr()
+
+class FormFieldInfo(FieldInfo):
+
+    def __init__(self, forminfo, input):
+        self.input = input
+        self.join = forminfo.name
+        self.column = '%s.%s' % (forminfo.tablename(), input.column)
+        self.label = input.label or input.column
+
+    def options(self):
+        return self.input.choices
+
+
+class ECFields(list):
+
+    def __init__(self, params):
+        self.params = params
+        self.cols = []
+        self.join = None
+        self.rows = None
+
+    def _get_field(self, field):
+        form, field = field.split(':')
+        if form:
+            fi = self.params.form_info(form)
+            input = fi.load().columns.find_input(field)
+            return FormFieldInfo(fi, input)
+        else:
+            field = self.params.demog_fields().field_by_name(field)
+            return DemogFieldInfo(field)
+
+    def add_field(self, name):
+        field = self._get_field(name)
+        field.index = len(self)
+        field.fields = self
+        if field.join:
+            assert self.join is None or self.join == field.join
+            self.join = field.join
+        self.append(field)
+        self.cols.append(field.column)
+        return field
+
+    def load(self, query, include_missing_forms):
+        if self.join:
+            if include_missing_forms:
+                join = 'LEFT JOIN'
+            else:
+                join = 'JOIN'
+            fi = self.params.form_info(self.join)
+            table = fi.tablename()
+            query.join(join + ' case_form_summary USING (case_id)')
+            query.join(join + ' %s ON (case_form_summary.summary_id = %s.summary_id AND NOT case_form_summary.deleted)' % (table, table))
+            #query.where('NOT case_form_summary.deleted')
+        self.rows = query.fetchcols(self.cols)
+
+    def get_column(self, index):
+        return [row[index] for row in self.rows]
+
+
+class EpiCurveParamsMixin:
+
+    show_epicurve = True
+
+    ts_outfmt = 'png'
+    ts_nbins = 'D1'
+    ts_bincol = ':onset_datetime'
+    ts_bincol2 = ''
+    ts_stacking = ''
+    ts_join = ''
+    ts_missing_forms = False
+    ts_stack_ratios = False
+    ts_stack_suppress = None
+
+    def available_outfmts(self):
+        return [
+            ('png', 'PNG'),
+            ('svg', 'SVG'),
+            ('pdf', 'PDF'),
+        ]
+
+    def available_nbins(self):
+        return [
+            ('N20', '20 bins'),
+            ('N50', '50 bins'),
+            ('N100', '100 bins'),
+            ('D1', '1 day'),
+            ('D2', '2 days'),
+            ('D3', '3 days'),
+            ('D4', '4 days'),
+            ('D5', '5 days'),
+            ('D6', '6 days'),
+            ('D7', '7 days'),
+            ('D14', '14 days'),
+            ('D28', '28 days'),
+        ]
+
+    def available_forms(self):
+        forms = [('', 'Case')]
+        for fi in self.all_form_info():
+            forms.append((fi.name, fi.label))
+        return forms
+
+    def available_bincol(self, allow_none=False):
+        options = []
+        if allow_none:
+            options.append(('', 'None'))
+        for field in self.demog_fields('report'):
+            if field.render == 'datetimeinput':
+                options.append((':%s' % field.name, field.label))
+        if self.ts_join:
+            fi = self.form_info(self.ts_join)
+            form_options = []
+            for input in fi.load().get_inputs():
+                if input.render == 'DateInput':
+                    form_options.append(((input.label or input.column),
+                                        '%s:%s' % (fi.name, input.column)))
+            form_options.sort()
+            for l, n in form_options:
+                options.append((n, l))
+        return options
+
+    def available_strata(self):
+        options = [('', 'None')]
+        for field in self.demog_fields('report'):
+            if field.optionexpr is not None and field.name != 'tags':
+                options.append((':%s' % field.name, field.label))
+        if self.ts_join:
+            fi = self.form_info(self.ts_join)
+            form_options = []
+            for input in fi.load().get_inputs():
+                if input.render in ('DropList', 'RadioList'):
+                    form_options.append(((input.label or input.column),
+                                        '%s:%s' % (fi.name, input.column)))
+            form_options.sort()
+            for l, n in form_options:
+                options.append((n, l))
+        return options
+
+    def available_values(self, field):
+        return ECFields(self)._get_field(field).options()
+
+    def _defaults(self, msgs):
+        if not config.enable_matplotlib:
+            msgs.msg('err', '%s not available on this system' % self.type_label)
+
+    def report(self, cred, msgs, filename=None):
+        self.check(msgs)
+        if msgs.have_errors():
+            return
+        ec = EpiCurve('\n'.join(self.title(cred).splitlines()))
+        # Fetch the data
+        include_deleted = False
+        if (self.ts_bincol == ':delete_timestamp'
+                or self.ts_bincol2 == ':delete_timestamp'
+                or self.ts_stacking == ':deleted'):
+            include_deleted = None
+        query = self.query(cred, include_deleted = include_deleted)
+        ecfields = ECFields(self)
+        stack_field = date2_field = None
+        date_field = ecfields.add_field(self.ts_bincol)
+        if self.ts_bincol2:
+            date2_field = ecfields.add_field(self.ts_bincol2)
+#        for col in ecfields.cols:
+#            query.where('%s IS NOT NULL' % col)
+        if self.ts_stacking:
+            stack_field = ecfields.add_field(self.ts_stacking)
+        ecfields.load(query, boolstr(self.ts_missing_forms))
+        if not ecfields.rows:
+            raise globals.Error('No records found')
+        ec.set_dates(date_field.data(), date_field.label)
+        if date2_field:
+            ec.set_lower_dates(date2_field.data(), date2_field.label)
+        ec.info(msgs)
+        if msgs.have_errors():
+            return
+        if stack_field:
+            ec.set_stack(stack_field.data(), stack_field.options(),
+                         label=stack_field.label, 
+                         ratios=boolstr(self.ts_stack_ratios),
+                         suppress=self.ts_stack_suppress)
+        if self.ts_nbins.startswith('N'):
+            ec.set_n_bins(int(self.ts_nbins[1:]))
+        elif self.ts_nbins.startswith('D'):
+            ec.set_n_days(int(self.ts_nbins[1:]))
+        return ImageReport(ec.graph(self.ts_outfmt, filename))
+
+    def _forms_used(self, used):
+        if self.ts_join:
+            used.add(self.ts_join)
+
+    def set_join(self, form):
+        if form:
+            if self.ts_join and self.ts_join != form:
+                raise Error('Can only specify one form (requested %s and %s)' %
+                            (self.ts_join, form))
+            self.ts_join = form
+
+    def _to_xml(self, xmlgen, curnode):
+        def field_attrs(e, field):
+            form, field = field.split(':')
+            if form:
+                e.attr('form', form)
+            e.attr('field', field)
+        e = xmlgen.push('epicurve')
+        e.boolattr('missing_forms', self.ts_missing_forms)
+        e.attr('format', self.ts_outfmt)
+        e.attr('nbins', self.ts_nbins)
+        e = xmlgen.push('dates')
+        field_attrs(e, self.ts_bincol)
+        xmlgen.pop()
+        if self.ts_bincol2:
+            e = xmlgen.push('dates')
+            field_attrs(e, self.ts_bincol2)
+            xmlgen.pop()
+        if self.ts_stacking:
+            e = xmlgen.push('stacking')
+            field_attrs(e, self.ts_stacking)
+            e.boolattr('ratios', self.ts_stack_ratios)
+            for suppress in self.ts_stack_suppress:
+                e = xmlgen.push('suppress')
+                e.text(suppress)
+                xmlgen.pop()
+            xmlgen.pop()
+        xmlgen.pop()
diff --git a/casemgr/reports/export.py b/casemgr/reports/export.py
new file mode 100644
index 0000000..9b9d879
--- /dev/null
+++ b/casemgr/reports/export.py
@@ -0,0 +1,271 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+
+import sys
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from casemgr import globals
+from casemgr import casetags
+from casemgr.reports.common import *
+
+
+class NS: pass
+
+
+class Preload(dict):
+
+    def __init__(self, colnames, query, keycol, values):
+        keyindex = colnames.index(keycol)
+        query.where_in(keycol, values)
+        for row in query.fetchcols(colnames):
+            ns = NS()
+            for colname, value in zip(colnames, row):
+                setattr(ns, colname, value)
+            key = row[keyindex]
+            self[row[keyindex]] = ns
+
+
+class DemogInfo(object):
+
+    def __init__(self, out_group):
+        self.name = None
+        columns = set(out_group.shared_columns)
+        try:
+            columns.remove('tags')
+        except KeyError:
+            self.load_tags = False
+        else:
+            self.load_tags = True
+        self.columns = list(columns)
+        self.instance_count = 1
+
+    def preload(self, case_ids):
+        query = globals.db.query('cases')
+        query.join('JOIN persons USING (person_id)')
+        preload = Preload(self.columns, query, 'case_id', case_ids)
+        if self.load_tags:
+            cases_tags = casetags.CasesTags(case_ids)
+            for ns in preload.itervalues():
+                tags = cases_tags.get(ns.case_id)
+                if tags:
+                    ns.tags = str(tags)
+        return preload
+
+
+class FormInfo(object):
+    # This object holds per-form info on:
+    #  * per-case summary_id lists
+    #  * max per-case instance counts
+
+    def __init__(self, out_group):
+        self.name = out_group.form_name
+        self.version = out_group.form_version
+        self.table = out_group.table
+        self.columns = list(out_group.shared_columns)
+        self.instance_count = 0
+        self.summ_by_case = {}
+
+    def add_summary(self, case_id, summary_id, version):
+        if self.version != version:
+            return        # XXX Error out? Ignore?
+        try:
+            case_form_summ = self.summ_by_case[case_id]
+        except KeyError:
+            case_form_summ = self.summ_by_case[case_id] = [summary_id]
+        else:
+            case_form_summ.append(summary_id)
+        if len(case_form_summ) > self.instance_count:
+            self.instance_count = len(case_form_summ)
+
+    def summ_for_cases(self, case_ids):
+        summ_for_cases = []
+        for case_id in case_ids:
+            try:
+                summ_for_cases.extend(self.summ_by_case[case_id])
+            except KeyError:
+                continue
+        return summ_for_cases
+
+    def preload(self, case_ids):
+        summ_ids = []
+        for case_id in case_ids:
+            case_summ = self.summ_by_case.get(case_id)
+            if case_summ is not None:
+                summ_ids.extend(case_summ)
+        query = globals.db.query(self.table)
+        return Preload(self.columns, query, 'summary_id', summ_ids)
+
+
+class ReportExportBase(object):
+
+    chunk_size = 500
+
+    def __init__(self, params, case_ids, 
+                       field_labels=True, strip_newlines=True):
+        self.params = params
+        self.case_ids = case_ids
+        self.field_labels = field_labels
+        self.strip_newlines = strip_newlines
+        self.init()
+        if self.forms:
+            self.scan_summaries()
+
+    def init(self):
+        self.out_groups = self.params.get_output_rows()
+        self.info_by_form = {}
+        self.info_by_form[None] = DemogInfo(self.out_groups[0])
+        self.forms = set()
+        for out_group in self.out_groups:
+            if not out_group.form_name:
+                continue
+            self.forms.add(out_group.form_name)
+            try:
+                form_info = self.info_by_form[out_group.form_name]
+                assert out_group.form_version == form_info.version
+            except KeyError:
+                self.info_by_form[out_group.form_name] = FormInfo(out_group)
+
+    def scan_summaries(self):
+        query = globals.db.query('case_form_summary',
+                                 order_by='form_date, summary_id')
+        query.where('NOT deleted')
+        query.where_in('form_label', self.forms)
+        query.where_in('case_id', self.case_ids)
+        cols = 'case_id', 'summary_id', 'form_label', 'form_version'
+        for case_id, summ_id, form_name, form_version in query.fetchcols(cols):
+            form_info = self.info_by_form[form_name]
+            form_info.add_summary(case_id, summ_id, form_version)
+
+    def outgroup_labels(self, out_group):
+        if self.field_labels:
+            return out_group.labels()
+        elif out_group.form_name:
+            return ['%s.%s' % (out_group.form_name, column)
+                    for column in out_group.columns]
+        else:
+            return out_group.columns
+
+    def yield_chunks(self):
+        for offs in range(0, len(self.case_ids), self.chunk_size):
+            yield self.case_ids[offs:offs+self.chunk_size]
+
+    def preload(self, case_ids):
+        preloads = {}
+        for info in self.info_by_form.values():
+            preloads[info.name] = info.preload(case_ids)
+        return preloads
+
+    ctrlre = re.compile(r'[\000-\037]+')
+
+    def render(self, out_group, ns):
+        if ns is None:
+            fields = [None] * len(out_group.fields)
+        else:
+            fields = out_group.as_list(ns)
+            if self.strip_newlines:
+                fields = [self.ctrlre.sub(' ', f) for f in fields]
+        return fields
+
+
+class ExportForms(ReportExportBase):
+
+    def init(self):
+        super(ExportForms, self).init()
+        self.summ_by_case = {}
+        if self.forms:
+            if len(self.forms) != 1:
+                raise Error('By-form export requires at most one form')
+            form_name, = self.forms
+            self.summ_by_case = self.info_by_form[form_name].summ_by_case
+
+    def header(self):
+        header = []
+        for out_group in self.out_groups:
+            header.extend(self.outgroup_labels(out_group))
+        return header
+
+    def __iter__(self):
+        yield self.header()
+        missing_form = [None]
+        for case_ids in self.yield_chunks():
+            preloads = self.preload(case_ids)
+            for case_id in case_ids:
+                summary_ids = self.summ_by_case.get(case_id, missing_form)
+                for summary_id in summary_ids:
+                    row = []
+                    for out_group in self.out_groups:
+                        if out_group.form_name:
+                            key = summary_id
+                        else:
+                            key = case_id
+                        ns = preloads[out_group.form_name].get(key)
+                        if ns is None:
+                            fields = [None] * len(out_group.fields)
+                        else:
+                            fields = out_group.as_list(ns)
+                        row.extend(fields)
+                    yield row
+
+
+class ExportCases(ReportExportBase):
+
+    def header(self):
+        header = []
+        if self.field_labels:
+            fmt = '%s (%s)'
+        else:
+            fmt = '%s.%s'
+        for out_group in self.out_groups:
+            labels = self.outgroup_labels(out_group)
+            inst_cnt = self.info_by_form[out_group.form_name].instance_count
+            if inst_cnt > 1:
+                for n in range(1, inst_cnt+1):
+                    for label in labels:
+                        header.append(fmt % (label, n))
+            else:
+                header.extend(labels)
+        return header
+
+    def __iter__(self):
+        yield self.header()
+        for case_ids in self.yield_chunks():
+            preloads = self.preload(case_ids)
+            for case_id in case_ids:
+                row = []
+                for out_group in self.out_groups:
+                    info = self.info_by_form[out_group.form_name]
+                    if info.name:
+                        keys = info.summ_by_case.get(case_id, ())
+                    else:
+                        keys = [case_id]
+                    for index in range(info.instance_count):
+                        try:
+                            key = keys[index]
+                        except IndexError:
+                            ns = None
+                        else:
+                            ns = preloads[info.name].get(key)
+                        if ns is None:
+                            fields = [None] * len(out_group.fields)
+                        else:
+                            fields = out_group.as_list(ns)
+                        row.extend(fields)
+                yield row
diff --git a/casemgr/reports/linereport.py b/casemgr/reports/linereport.py
new file mode 100644
index 0000000..90b96a0
--- /dev/null
+++ b/casemgr/reports/linereport.py
@@ -0,0 +1,171 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+from itertools import izip
+
+from cocklebur import form_ui
+
+from casemgr import globals, casetags
+from casemgr.reports.common import *
+
+debug = 0
+
+def get_formdata_by_case(output_rows, case_ids):
+    forms = []
+    summids_by_form = {}
+    for og in output_rows:
+        if og.form_name:
+            summids_by_form[(og.form_name, og.form_version)] = []
+            forms.append(og.form_name)
+    if not case_ids or not summids_by_form:
+        return {}
+    query = globals.db.query('case_form_summary', order_by='form_date')
+    query.where('NOT deleted')
+    query.where_in('form_label', forms)
+    query.where_in('case_id', case_ids)
+    formdata_by_summid = {}
+    summids_by_case_by_form = {}
+    for case_id in case_ids:
+        summids_by_case_by_form[case_id] = {}
+    # Fetch summary_id for cases
+    cols = 'case_id', 'form_label', 'form_version', 'summary_id'
+    for case_id, name, version, summary_id in query.fetchcols(cols):
+        case_form_summids = summids_by_case_by_form[case_id]
+        try:
+            summids_by_form[(name, version)].append(summary_id)
+        except KeyError:
+            formdata_by_summid[summary_id] = 'Omitted data from incompatible form %r, version %s' % (name, version)
+        case_form_summids.setdefault(name, []).append(summary_id)
+    # Now fetch form data by form
+    for (form_name, form_version), summids in summids_by_form.iteritems():
+        if summids:
+            table = globals.formlib.tablename(form_name, form_version)
+            cols = output_rows.tablecols(table)
+            query = globals.db.query(table)
+            query.where_in('summary_id', summids)
+            for formdata in query.fetchcols(cols):
+                formdata = dict(izip(cols, formdata))
+                formdata_by_summid[formdata['summary_id']] = formdata
+    if debug: print >> sys.stderr, '%d cases, %d forms, %d summaries' % (len(case_ids), len(summids_by_form), len(formdata_by_summid))
+    # Now collate form data by cases
+    formdata_by_case_by_form = {}
+    for case_id in case_ids:
+        case_formdata = formdata_by_case_by_form.setdefault(case_id, {})
+        case_form_summids = summids_by_case_by_form[case_id]
+        for og in output_rows:
+            if og.form_name:
+                form_formdata = case_formdata.setdefault(og.form_name, [])
+                form_summids = case_form_summids.get(og.form_name, [])
+                for summid in form_summids:
+                    try:
+                        form_formdata.append(formdata_by_summid[summid])
+                    except KeyError:
+                        pass
+    return formdata_by_case_by_form
+
+
+class GenCase:
+    def __init__(self, id):
+        self.id = id
+        self.columns = None
+        self.freetext = []
+
+    def addtext(self, label, text, key=''):
+        if text:
+            self.freetext.append((key, label, text))
+
+class NS:
+    def __init__(self, vars):
+        self.__dict__ = vars
+
+class ReportChunks:
+    render = 'table'
+    chunk_size = 100
+
+    def __init__(self, reportparams, case_ids):
+        self.init(reportparams, case_ids)
+
+    def init(self, reportparams, case_ids):
+        self.params = reportparams
+        self.case_ids = case_ids
+        self.__preload = None
+        self.__preload_offset = 0
+        self.output_rows = reportparams.get_output_rows()
+        self.n_cols = len(self.output_rows[0].labels())
+        self.caseperson_cols = list(self.output_rows.tablecols('caseperson'))
+        self.load_tags = 'tags' in self.caseperson_cols
+        if self.load_tags:
+            self.caseperson_cols.remove('tags')
+
+    def __getstate__(self):
+        return self.params, self.case_ids
+
+    def __setstate__(self, state):
+        self.init(*state)
+
+    def __len__(self):
+        return len(self.case_ids)
+
+    def headings(self):
+        return self.output_rows[0].labels()
+
+    def load_chunk(self, offset):
+        window = self.case_ids[offset:offset+self.chunk_size]
+        if not window:
+            raise IndexError
+        formdata = get_formdata_by_case(self.output_rows, window)
+        query = globals.db.query('cases')
+        query.join('JOIN persons USING (person_id)')
+        query.where_in('case_id', window)
+        self.__preload = {}
+        self.__preload_offset = offset
+        if self.load_tags:
+            cases_tags = casetags.CasesTags(window)
+        for caseperson in query.fetchcols(self.caseperson_cols):
+            caseperson = dict(izip(self.caseperson_cols, caseperson))
+            case_id = caseperson['case_id']
+            if self.load_tags:
+                tags = cases_tags.get(case_id)
+                if tags:
+                    caseperson['tags'] = str(tags)
+            gencase = self.__preload[case_id] = GenCase(case_id)
+            gencase.columns = self.output_rows[0].as_list(NS(caseperson))
+            for og in self.output_rows[1:]:
+                if not og.form_name:
+                    gencase.addtext(og.label, og.as_text(NS(caseperson)),
+                                    case_id)
+                else:
+                    for values in formdata[case_id][og.form_name]:
+                        if isinstance(values, basestring):
+                            gencase.addtext(None, values)
+                        else:
+                            form_id = form_ui.form_id(values['summary_id'])
+                            gencase.addtext('%s %s' % (og.label, form_id), 
+                                            og.as_text(NS(values)), form_id)
+
+    def __getitem__(self, i):
+        if (self.__preload is None or i < self.__preload_offset 
+                or i >= self.__preload_offset + self.chunk_size):
+            self.load_chunk(i)
+        try:
+            return self.__preload[self.case_ids[i]]
+        except KeyError:
+            # This should not happen
+            res = GenCase(self.case_ids[i])
+            res.addtext(None, 'System Case ID %s not found' % self.case_ids[i])
+            return res
diff --git a/casemgr/reports/report.py b/casemgr/reports/report.py
new file mode 100644
index 0000000..fecc24c
--- /dev/null
+++ b/casemgr/reports/report.py
@@ -0,0 +1,358 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import introspect, template
+from casemgr import globals, demogfields, caseaccess, syndrome, messages
+from casemgr.reports import reportfilters, reportcolumns, linereport, \
+                            epicurve, contactvis, store, crosstab, export
+from casemgr.reports.common import *
+
+import config
+
+class OrderParam:
+
+    def __init__(self, column='', direction='asc'):
+        self.col = column
+        self.rev = direction
+
+
+class OrderbyParamsMixin:
+
+    show_orderby = True
+
+    rev_options = [
+        ('asc', 'Ascending'),
+        ('desc', 'Descending'),
+    ]
+
+    def init(self):
+        self.order_by = []
+
+    def add_order(self, **kw):
+        self.order_by.append(OrderParam(**kw))
+
+    def del_order(self, index):
+        del self.order_by[index]
+
+    def query_order(self):
+        f = self.demog_fields().field_by_name
+        return ','.join(['%s %s' % (f(o.col).field_result, o.rev) 
+                         for o in self.order_by
+                         if o.col])
+
+    def order_cols(self): 
+        avail_cols = []
+        for f in self.demog_fields('report'):
+            if f.name != 'case_definition' and getattr(f, 'field_result', None):
+                avail_cols.append((f.name, f.label))
+        return avail_cols
+
+    def _to_xml(self, xmlgen, curnode):
+        if self.order_by:
+            xmlgen.push('ordering')
+            for ob in self.order_by:
+                e = xmlgen.push('orderby')
+                e.attr('column', ob.col)
+                e.attr('direction', ob.rev)
+                xmlgen.pop()
+            xmlgen.pop()
+
+
+class FormDependancyMixin:
+
+    def init(self):
+        self.saved_formdeps = []
+
+    def _check(self, msgs):
+        for saved_info in self.saved_formdeps:
+            info = self.form_info(saved_info['name'])
+            if info is None:
+                msgs.msg('err', 'Form "%s" is no longer available - check '
+                                'report parameters' % saved_info['label'])
+            elif info.version != saved_info['version']:
+                msgs.msg('warn', 'Form "%s" has been updated - check report '
+                                 'parameters (report version %r, now '
+                                 'version %r)' % (saved_info['label'], 
+                                    saved_info['version'], info.version))
+
+    def _to_xml(self, xmlgen, curnode):
+        for form in self.forms_used():
+            info = self.form_info(form)
+            e = xmlgen.push('formdep')
+            e.attr('name', info.name)
+            e.attr('version', info.version)
+            e.attr('label', info.label)
+            xmlgen.pop()
+
+
+class ReportParamsBase:
+
+    report_type = None
+    type_label = None
+    header = None
+    order_by = None
+
+    show_filters = False
+    show_orderby = False
+    show_columns = False
+    show_axes = False
+    show_epicurve = False
+    show_contactvis = False
+    show_headfoot = False
+
+    def __init__(self, syndrome_id):
+        self.syndrome_id = syndrome_id
+        introspect.callall(self, 'init')
+
+    def expand(self, cred, name):
+        assert name in ('header', 'preamble', 'footer')
+        synd = syndrome.syndromes[self.syndrome_id]
+        value = getattr(self, name)
+        value = template.expand_template(value, 
+                                         username=cred.user.username,
+                                         fullname=cred.user.fullname,
+                                         role=cred.unit.name,
+                                         unit=cred.unit.name,
+                                         syndrome=synd.name,
+                                         syndrome_id=synd.syndrome_id,
+                                         syndrome_name=synd.name,
+                                         syndrome_desc=synd.description)
+        return value
+
+    def change_type(self, report_type, msgs=None):
+        report_ctor = get_report_ctor(report_type)
+        new = report_ctor(self.syndrome_id)
+        new.__dict__.update(self.__dict__)
+        new.defaults(msgs)
+        return new
+
+    def title(self, creds):
+        if self.header:
+            return self.expand(creds, 'header')
+        if self.label:
+            return self.label
+        synd = syndrome.syndromes[self.syndrome_id]
+        return '%s %s' % (synd.name, self.type_label)
+
+    def demog_fields(self, context=None):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        if context is not None:
+            fields = fields.context_fields(context)
+        return fields
+
+    def all_form_info(self):
+        return syndrome.syndromes[self.syndrome_id].all_form_info()
+
+    def form_info(self, form_name):
+        return syndrome.syndromes[self.syndrome_id].form_info(form_name)
+
+    def defaults(self, msgs=None):
+        if msgs is None:
+            msgs = messages.Messages()
+        introspect.callall(self, '_defaults', msgs)
+        return msgs
+
+    def check(self, msgs=None):
+        """
+        After loading, base classes may need to warn the user about
+        things such as form version skew.
+        """
+        if msgs is None:
+            msgs = messages.Messages()
+        introspect.callall(self, '_check', msgs)
+        return msgs
+
+    def cols_update(self):
+        """
+        Called prior to processing user request to update column options
+        """
+        introspect.callall(self, '_cols_update')
+
+    def deleted_filter(self):
+        return False
+
+    def query(self, cred, no_order=False, form_based=False, 
+                          include_deleted=False):
+        kw = {}
+        if self.order_by and not no_order:
+            kw['order_by'] = self.query_order()
+        if self.deleted_filter():
+            include_deleted = None
+        query = globals.db.query('cases', **kw)
+        query.join('JOIN persons USING (person_id)')
+        caseaccess.acl_query(query, cred, deleted=include_deleted)
+        query.where('syndrome_id = %s', self.syndrome_id)
+        if self.show_filters:
+            self.filter_query(query, form_based=form_based)
+        return query
+
+    def count(self, cred):
+        return self.query(cred, no_order=True).aggregate('count(case_id)')
+
+    def get_case_ids(self, cred):
+        return self.query(cred).fetchcols('case_id')
+
+    def forms_used(self):
+        used = set()
+        introspect.callall(self, '_forms_used', used)
+        return used
+
+    def to_xml(self, xmlgen):
+        curnode = xmlgen.push('report')
+        curnode.attr('type', self.report_type)
+        curnode.attr('name', self.label)
+        xmlgen.pushtext('header', self.header)
+        if self.syndrome_id is not None:
+            synd = syndrome.syndromes[self.syndrome_id]
+            xmlgen.pushtext('syndrome', synd.name)  # Informational
+        introspect.callall(self, '_to_xml', xmlgen, curnode)
+        xmlgen.pop()
+
+
+class HeadFootParamsMixin:
+
+    show_headfoot = True
+
+    preamble = ''
+    footer = ''
+
+    def _defaults(self, msgs):
+        if not self.preamble:
+            synd = syndrome.syndromes[self.syndrome_id]
+            self.preamble = synd.description
+
+    def _to_xml(self, xmlgen, curnode):
+        xmlgen.pushtext('preamble', self.preamble)
+        xmlgen.pushtext('footer', self.footer)
+
+
+report_types = {}
+
+class LineReportParams(
+                FormDependancyMixin,
+                reportfilters.FilterParamsMixin, 
+                reportcolumns.OutcolsParamsMixin, 
+                OrderbyParamsMixin, 
+                HeadFootParamsMixin,
+                store.ParamSaveMixin,
+                ReportParamsBase):
+
+    report_type = 'line'
+    type_label = 'Line Report'
+
+    export_strip_newlines = 'yes'
+    export_column_labels = 'fields'
+    export_row_type = 'forms'
+
+    def report(self, cred, msgs):
+        self.check(msgs)
+        if msgs.have_errors():
+            return
+        return linereport.ReportChunks(self, self.get_case_ids(cred))
+
+    def export_rows(self, cred):
+        if self.export_row_type == 'forms':
+            exporter = export.ExportForms
+        elif self.export_row_type == 'cases':
+            exporter = export.ExportCases
+        else:
+            raise Error('Select an row export scheme')
+        return exporter(self, self.get_case_ids(cred), 
+                        field_labels=(self.export_column_labels == 'fields'),
+                        strip_newlines=(self.export_strip_newlines == 'yes'))
+
+    def _to_xml(self, xmlgen, curnode):
+        e = xmlgen.push('export')
+        e.boolattr('strip_newlines', self.export_strip_newlines)
+        e.attr('column_labels', self.export_column_labels)
+        e.attr('row_type', self.export_row_type)
+        xmlgen.pop()
+
+report_types[LineReportParams.report_type] = LineReportParams
+
+ReportParams = LineReportParams                 # Legacy: old pickles
+
+
+class EpicurveParams(
+                FormDependancyMixin,
+                reportfilters.FilterParamsMixin, 
+                epicurve.EpiCurveParamsMixin,
+                store.ParamSaveMixin,
+                ReportParamsBase):
+
+    report_type = 'epicurve'
+    type_label = 'Epi Curve'
+
+report_types[EpicurveParams.report_type] = EpicurveParams
+
+
+class CrosstabReportParams(
+                FormDependancyMixin,
+                crosstab.CrosstabParams,
+                reportfilters.FilterParamsMixin,
+                store.ParamSaveMixin,
+                ReportParamsBase):
+
+    report_type = 'crosstab'
+    type_label = 'Crosstab'
+
+
+report_types[CrosstabReportParams.report_type] = CrosstabReportParams
+
+
+class ContactVisReportParams(
+                FormDependancyMixin,
+                contactvis.ContactVisParamsMixin,
+                reportfilters.FilterParamsMixin,
+                store.ParamSaveMixin,
+                ReportParamsBase):
+
+    report_type = 'contactvis'
+    type_label = '%s visualisation' % config.contact_label
+
+report_types[ContactVisReportParams.report_type] = ContactVisReportParams
+
+
+def report_type_optionexpr():
+    options = [(rt.type_label, rt.report_type) for rt in report_types.values()]
+    options.sort()
+    return [(b, a) for a, b in options]
+
+
+def get_report_ctor(report_type):
+    try:
+        return report_types[report_type]
+    except KeyError:
+        raise KeyError('Unknown report type %r' % report_type)
+
+def new_report(syndrome_id, report_type='line'):
+    report_ctor = get_report_ctor(report_type)
+    new = report_ctor(syndrome_id)
+    new.defaults()
+    return new
+
+
+def type_label(report_type):
+    try:
+        return report_types[report_type].type_label
+    except KeyError:
+        return '???'
diff --git a/casemgr/reports/reportcolumns.py b/casemgr/reports/reportcolumns.py
new file mode 100644
index 0000000..9985ec8
--- /dev/null
+++ b/casemgr/reports/reportcolumns.py
@@ -0,0 +1,426 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import agelib
+from casemgr import globals
+from casemgr.reports.common import *
+
+import config
+
+class LineOutGenBase:
+
+    def __init__(self, outgroup):
+        self.label = outgroup.label
+        self.form_name = None
+        self.table = None
+        self.columns = []
+        self.fields = []
+
+    def get_table_columns(self):
+        return self.table, self.columns
+
+    def labels(self):
+        return [label for label, column, outtrans in self.fields]
+
+    def as_text(self, ns):
+        result = []
+        for label, column, outtrans in self.fields:
+            value = outtrans(ns)
+            if value is not None:
+                result.append('%s: %s' % (label, value))
+        return ', '.join(result)
+
+    def as_list(self, ns):
+        return [outtrans(ns) for label, column, outtrans in self.fields]
+
+
+class DemogLineOut(LineOutGenBase):
+
+    def __init__(self, outgroup):
+        LineOutGenBase.__init__(self, outgroup)
+        self.table = 'caseperson'
+        field_by_name = outgroup.params.demog_fields().field_by_name
+        for og in outgroup:
+            if og.name == 'DOB_only':
+                def DOB_outtrans(ns):
+                    return agelib.dob_if_dob(ns.DOB, ns.DOB_prec)
+                self.columns.extend(['DOB', 'DOB_prec'])
+                self.fields.append((og.label, None, DOB_outtrans))
+            elif og.name == 'age':
+                def Age_outtrans(ns):
+                    return agelib.agestr(ns.DOB)
+                self.columns.extend(['DOB', 'DOB_prec'])
+                self.fields.append((og.label, None, Age_outtrans))
+            else:
+                demog_field = field_by_name(og.name)
+                column = demog_field.field_result
+                self.columns.append(column)
+                if demog_field.name == 'DOB':
+                    self.columns.append('DOB_prec')
+                self.fields.append((og.label, column, demog_field.outtrans))
+
+
+class FormLineOut(LineOutGenBase):
+
+    def __init__(self, outgroup):
+        LineOutGenBase.__init__(self, outgroup)
+        info = outgroup.form_info()
+        self.form_name = info.name
+        self.form_version = info.version
+        info.load()
+        self.table = info.tablename()
+        form = info.load()
+        for og in outgroup:
+            input = form.columns.find_input(og.name)
+            columns = input.get_column_names()
+            self.columns.extend(columns)
+            if len(columns) == 1:
+                columns = columns[0]
+            else:
+                columns = tuple(columns)
+            self.fields.append((og.label, columns, input.outtrans))
+
+
+class OutputRows(list):
+
+    def __init__(self, reportparams):
+        self.cols_by_table = {
+            'caseperson': set(['case_id'])
+        }
+        for outgroup in reportparams.outgroups:
+            lineout = outgroup.get_outgen()
+            self.append(lineout)
+            table, cols = lineout.get_table_columns()
+            try:
+                table_cols = self.cols_by_table[table]
+            except KeyError:
+                table_cols = self.cols_by_table[table] = set(['summary_id'])
+            table_cols.update(cols)
+            lineout.shared_columns = table_cols
+
+    def tablecols(self, table):
+        return self.cols_by_table[table]
+
+
+class ReportCol:
+
+    def __init__(self, report_columns, name, label=None):
+        self.report_columns = report_columns
+        self.name = name
+        if label is None:
+            label = self.canonical_label()
+        self.label = label
+
+    def canonical_label(self):
+        return self.report_columns.canonical_label(self.name)
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('column')
+        e.attr('name', self.name)
+        e.attr('label', self.label)
+        xmlgen.pop()
+
+
+class ReportColsBase(list):
+
+    addcol = None
+    form_name = None
+    is_caseperson = False
+    is_form = False
+
+    def __init__(self, params, label):
+        self.params = params
+        self.label = label
+        self.initial_label = label
+
+    def colop(self, op, index=None):
+        if op == 'up':
+            if index > 0:
+                self[index], self[index - 1] = self[index - 1], self[index]
+        elif op == 'dn':
+            if index < len(self) - 1:
+                self[index], self[index + 1] = self[index + 1], self[index]
+        elif op == 'del':
+            del self[index]
+        elif op == 'clear':
+            del self[:]
+
+    def add(self, name, label=None):
+        self.append(ReportCol(self, name, label))
+
+    def cols_update(self):
+        if self.addcol:
+            if self.addcol.startswith('!'):
+                name = self.addcol[1:]
+                meth = getattr(self, 'add_' + name, None)
+                if meth is not None:
+                    meth()
+            else:
+                for name in self.addcol.split(','):
+                    self.add(name)
+            self.addcol = None
+
+    def names(self):
+        return [c.name for c in self]
+
+    def labels(self):
+        return [c.label for c in self]
+
+    def _check(self, msgs):
+        pass
+
+
+class DemogCols(ReportColsBase):
+
+    combos = [
+        ('surname,given_names', 'Surname and given names'),
+        ('home_phone,work_phone,mobile_phone', 'Phone numbers'),
+        ('street_address,locality,state,postcode', 'Primary address'),
+        ('alt_street_address,alt_locality,alt_state,alt_postcode', 
+            'Secondary address'),
+    ]
+    is_caseperson = True
+
+    def canonical_label(self, name):
+        if name == 'DOB_only':
+            return 'Date of birth'
+        elif name == 'age':
+            return 'Age'
+        return self.params.demog_fields().field_by_name(name).label
+
+    def available_cols(self): 
+        used = set(self.names())
+        used.add('case_definition')
+        avail = set()
+        for f in self.params.demog_fields('report'):
+            if getattr(f, 'field_result', None):
+                avail.add(f.name)
+        avail_cols = [('', '- Choose a field -')]
+        for cols, label in self.combos:
+            fields = [f for f in cols.split(',') if f in avail]
+            if fields:
+                for col in fields:
+                    if col in used:
+                        break
+                else:
+                    avail_cols.append((','.join(fields), label))
+        other_cols = []
+        if 'DOB' in avail:
+            other_cols.append(('Date of birth', 'DOB_only'))
+            other_cols.append(('Age', 'age'))
+        for f in self.params.demog_fields('report'):
+            if f.name not in used and getattr(f, 'field_result', None):
+                other_cols.append((f.label, f.name))
+        other_cols.sort()
+        for l, n in other_cols:
+            avail_cols.append((n, l))
+        return avail_cols
+
+    def get_outgen(self):
+        return DemogLineOut(self)
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('group')
+        e.attr('type', 'demog')
+        e.attr('label', self.label)
+        for c in self:
+            c.to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class FormCols(ReportColsBase):
+
+    is_form = True
+    common_avail = [
+        ('', '- Choose a field -'),
+        ('!all', '- All fields -'),
+        ('!summary', '- Summary fields -'),
+    ]
+
+    def __init__(self, params, label, form_name):
+        ReportColsBase.__init__(self, params, label)
+        self.form_name = form_name
+
+    def form_info(self):
+        return self.params.form_info(self.form_name)
+
+    def getinput(self, name):
+        return self.form_info().load().columns.find_input(name)
+
+    def canonical_label(self, name):
+        input = self.getinput(name)
+        return input.label or input.column
+
+    def available_cols(self): 
+        used = set(self.names())
+        avail_cols = list(self.common_avail)
+        other_cols = []
+        for input in self.form_info().load().get_inputs():
+            name = input.column.lower()
+            if name not in used:
+                other_cols.append((input.label or input.column, name))
+        other_cols.sort()
+        for l, n in other_cols:
+            avail_cols.append((n, l))
+        return avail_cols
+
+    def add_all(self): 
+        used = set(self.names())
+        for input in self.form_info().load().get_inputs():
+            name = input.column.lower()
+            if name not in used:
+                self.add(name, input.label or input.column)
+
+    def add_summary(self): 
+        used = set(self.names())
+        for input in self.form_info().load().get_inputs():
+            name = input.column.lower()
+            if name not in used and input.label:
+                self.add(name, input.label)
+
+    def get_outgen(self):
+        return FormLineOut(self)
+
+    def _check(self, msgs):
+        form = self.form_info().load()
+        new_cols = []
+        for c in self:
+            try:
+                form.columns.find_input(c.name)
+            except KeyError:
+                msgs.msg('err', 'Form %r has been updated, report field %r has '
+                                'been deleted' % (form.label, c.label))
+            else:
+                c.name = c.name.lower()
+                new_cols.append(c)
+        if self != new_cols:
+            self[:] = new_cols
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('group')
+        e.attr('type', 'form')
+        e.attr('form', self.form_name)
+        e.attr('label', self.label)
+        for c in self:
+            c.to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class OutcolsParamsMixin:
+
+    show_columns = True
+
+    def init(self):
+        self.outgroups = []
+
+    def _defaults(self, msgs):
+        if not self.outgroups:
+            stoplist = set(('case_definition', 'deleted'))
+            cols = DemogCols(self, 'Columns')
+            for field in self.demog_fields('report'):
+                if field.show_result and field.name not in stoplist:
+                    cols.add(field.name)
+            self.outgroups.append(cols)
+            self.outgroups.append(DemogCols(self, 'Additional information'))
+
+    def has_up(self, i):
+        return (i > 1 
+                and self.outgroups[i-1].is_form == self.outgroups[i].is_form)
+
+    def has_dn(self, i):
+        return (i > 0 and i < (len(self.outgroups) - 1)
+                and self.outgroups[i+1].is_form == self.outgroups[i].is_form)
+
+    def colop(self, op, group_idx, col_idx=None):
+        if op == 'gup':
+            if self.has_up(group_idx):
+                self.outgroups[group_idx], self.outgroups[group_idx - 1] =\
+                    self.outgroups[group_idx - 1], self.outgroups[group_idx]
+        elif op == 'gdn':
+            if self.has_dn(group_idx):
+                self.outgroups[group_idx], self.outgroups[group_idx + 1] =\
+                    self.outgroups[group_idx + 1], self.outgroups[group_idx]
+        elif op == 'gdel':
+            if group_idx > 0:
+                del self.outgroups[group_idx]
+        else:
+            return self.outgroups[group_idx].colop(op, col_idx)
+
+    def _cols_update(self):
+        for outgroup in self.outgroups:
+            outgroup.cols_update()
+
+    def available_col_forms(self):
+        if not config.form_rollforward:
+            return []
+        available = [('', '- Choose a form -')]
+        for info in self.all_form_info():
+            available.append((info.name, info.label))
+        return available
+
+    def add_caseperson(self):
+        cols = DemogCols(self, 'Additional information')
+        for i, outgroup in enumerate(self.outgroups):
+            if outgroup.is_form:
+                self.outgroups.insert(i, cols)
+                break
+        else:
+            self.outgroups.append(cols)
+
+    def add_form(self, add_name):
+        info = self.form_info(add_name)
+        if info is not None:
+            self.outgroups.append(FormCols(self, info.label, info.name))
+
+    def add_group(self, type, label, form=None):
+        if type == 'demog':
+            group = DemogCols(self, label)
+        elif type == 'form':
+            group = FormCols(self, label, form)
+        else:
+            raise Error('Unknown report group type %r' % type)
+        self.outgroups.append(group)
+        return group
+
+    def get_output_rows(self):
+        return OutputRows(self)
+
+    def _check(self, msgs):
+        new_outgroups = []
+        for og in self.outgroups:
+            if og.form_name:
+                if self.form_info(og.form_name) is None:
+                    continue
+                og._check(msgs)
+            new_outgroups.append(og)
+        self.outgroups = new_outgroups
+
+    def _forms_used(self, used):
+        for outgroup in self.outgroups:
+            if outgroup.form_name:
+                used.add(outgroup.form_name)
+
+    def _to_xml(self, xmlgen, curnode):
+        xmlgen.push('groups')
+        for outgroup in self.outgroups:
+            outgroup.to_xml(xmlgen)
+        xmlgen.pop()
diff --git a/casemgr/reports/reportfilters.py b/casemgr/reports/reportfilters.py
new file mode 100644
index 0000000..c643587
--- /dev/null
+++ b/casemgr/reports/reportfilters.py
@@ -0,0 +1,1041 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+import inspect
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj, form_ui, datetime, agelib, utils
+from casemgr import demogfields, fuzzyperson, syndrome, globals, casetags
+from casemgr.reports.common import *
+
+# FieldOps support the Terms, providing field specific methods for date
+# formating, valid discrete values, and so on. Important subclasses include:
+#   DemogFieldOps
+#   FormFieldOps
+#   PatternFieldOps
+#   RangeFieldOps
+#   DateRangeFieldOps
+#
+# FieldOpsCache provides an accellerated lookup of FieldOp class from field
+# name (and form+version).
+#
+# FilterBase is the basis for filter nodes. The TermBase subclass is the basis
+# for filter terms such as "in", "pattern", "phonetic", "range", etc.
+# Conjunction provides the AND and OR operators.
+#
+# FilterGroup is a helper for the FilterAdder, used when listing available
+# filters and filter operators. Key subclasses are:
+#   DemogFilterGroup
+#   FormFilterGroup
+#   AndSubexpressionGroup
+#   OrSubexpressionGroup
+#
+# FilterAdder holds the user context while the user is adding a new filter term.
+#
+# FilterParamsMixin is the top level structure that holds filter-related report
+# parameters.
+
+class FieldOps(object):
+
+    allow_negate = True
+
+    def __init__(self, term):
+        self.params = term.params
+        self.term = term
+
+    def date_format(self):
+        return None
+
+    def check(self, msgs):
+        return True
+
+    def query(self, query):
+        if boolstr(self.term.negate):
+            query = query.sub_expr(conjunction='AND', negate=True)
+        try:
+            self._query(query)
+        except Error, e:
+            raise Error('%s filter: %s' % (self.label(), e))
+
+
+class PatternFieldOps(FieldOps):
+
+    def _query(self, query):
+        if self.term.value:
+            values = utils.commasplit(self.term.value)
+            colname = self.colname()
+            if len(values) > 1:
+                query = query.sub_expr(conjunction='OR')
+                for value in values:
+                    query.where('%s ILIKE %%s' % colname, dbobj.wild(value))
+            else:
+                query.where('%s ILIKE %%s' % colname, 
+                            dbobj.wild(self.term.value))
+
+
+class RangeFieldOps(FieldOps):
+
+    iso_fmt = None
+
+    def parse(self, v):
+        return v
+
+    def _query(self, query):
+        colname = self.colname()
+        from_value = to_value = None
+        if self.term.from_value:
+            from_value = self.parse(self.term.from_value)
+        if self.term.to_value:
+            to_value = self.parse(self.term.to_value)
+        if from_value and to_value and to_value < from_value:
+            to_value, from_value = from_value, to_value
+        if from_value:
+            from_op = '>'
+            if boolstr(self.term.incl_from):
+                from_op += '='
+            query.where('%s %s %%s' % (colname, from_op), from_value)
+        if to_value:
+            to_op = '<'
+            if boolstr(self.term.incl_to):
+                to_op += '='
+            query.where('%s %s %%s' % (colname, to_op), to_value)
+
+
+class DateRangeFieldOps(RangeFieldOps):
+
+    iso_fmt = '%Y-%m-%d %H:%M:%S'
+
+    def date_format(self):
+        return datetime.mx_parse_datetime.format
+
+    def parse(self, v):
+        try:
+            return datetime.parse_discrete(v, past=True).mx()
+        except datetime.Error, e:
+            raise Error(str(e))
+
+
+class DemogFieldOps(FieldOps):
+
+    def form_label(self):
+        return 'Demographics'
+
+    def demog_field(self):
+        return self.params.demog_fields().field_by_name(self.term.field)
+
+    def label(self):
+        return self.demog_field().label
+
+    def colname(self):
+        field = self.demog_field()
+        return '%s.%s' % (field.table, field.name)
+
+    def options(self):
+        return [(v or '?', l) for v, l in self.demog_field().optionexpr()]
+
+    def _query(self, query):
+        if self.term.value:
+            query.where('%s = %%s' % self.colname(), self.term.value)
+
+
+class CasesetDemogFieldOps(DemogFieldOps):
+
+    def _query(self, query):
+        query.where_in('case_id', [int(v) for v in self.term.values])
+
+
+class PatternDemogFieldOps(PatternFieldOps, DemogFieldOps):
+
+    pass
+
+
+class RangeDemogFieldOps(RangeFieldOps, DemogFieldOps):
+
+    pass
+
+
+class DateRangeDemogFieldOps(DateRangeFieldOps, DemogFieldOps):
+
+    pass
+
+
+class DOBDemogFieldOps(DateRangeDemogFieldOps):
+
+    def parse(self, v):
+        if v:
+            try:
+                return agelib.parse_dob_or_age(v)[0]
+            except agelib.Error, e:
+                raise Error('%s filter: %s' % (self.label(), e))
+
+
+class InDemogFieldOps(DemogFieldOps):
+
+    def _query(self, query):
+        if self.term.values:
+            if '?' in self.term.values:
+                sub = query.sub_expr('OR')
+                sub.where('%s IS NULL' % self.colname())
+                value = [v for v in self.term.values if v != '?']
+                if value:
+                    sub.where_in(self.colname(), value)
+            else:
+                query.where_in(self.colname(), self.term.values)
+
+
+class NamesDemogFieldOps(DemogFieldOps):
+
+    def _query(self, query):
+        if self.term.value:
+            try:
+                fuzzyperson.find(query, self.term.value)
+            except ValueError, e:
+                raise Error(str(e))
+
+
+class DeletedDemogFieldOps(DemogFieldOps):
+
+    allow_negate = False
+
+    def query(self, query):
+        if self.term.value == 'exclude':
+            query.where('NOT cases.deleted')
+        elif self.term.value == 'only':
+            query.where('cases.deleted')
+
+    def options(self):
+        return [
+            ('exclude', 'Excluded'),
+            ('both', 'Included'),
+            ('only', 'Only deleted'),
+        ]
+
+
+class TagsDemogFieldOps(DemogFieldOps):
+
+    def _query(self, query):
+        if self.term.value:
+            subq = query.in_select('cases.case_id', 'case_tags', 
+                                   columns=['case_id'])
+            subq.join('JOIN tags USING (tag_id)')
+            subq.where('tag ILIKE %s', self.term.value)
+
+
+class FormFieldOps(FieldOps):
+
+    def form_input(self):
+        form = self.params.form_info(self.term.form).load()
+        return form.columns.find_input(self.term.field)
+
+    def form_label(self):
+        return self.params.form_info(self.term.form).label
+
+    def label(self):
+        input = self.form_input()
+        return input.label or input.column
+
+    def formtable(self):
+        return self.params.form_info(self.term.form).tablename()
+
+    def colname(self):
+        return '%s.%s' % (self.formtable(), self.term.field)
+
+    def check(self, msgs):
+        info = self.params.form_info(self.term.form)
+        try:
+            info.load().columns.find_input(self.term.field)
+        except KeyError:
+            msgs.msg('err', 'Form %r has been updated, filter field %r has '
+                            'been deleted' % (info.label, self.term.field))
+            return False
+        return True
+
+
+class PatternFormFieldOps(PatternFieldOps, FormFieldOps):
+
+    pass
+
+
+class RangeFormFieldOps(RangeFieldOps, FormFieldOps):
+
+    pass
+
+
+class DateRangeFormFieldOps(DateRangeFieldOps, FormFieldOps):
+
+    iso_fmt = '%Y-%m-%d'
+
+    def date_format(self):
+        return datetime.mx_parse_date.format
+
+
+class DateTimeRangeFormFieldOps(DateRangeFieldOps, FormFieldOps):
+
+    def date_format(self):
+        return datetime.mx_parse_datetime.format
+
+
+class ChoicesFormFieldOps(FormFieldOps):
+
+    def _query(self, query):
+        if self.term.values:
+            query.where_in(self.colname(), self.term.values)
+
+    def options(self):
+        return self.form_input().choices
+
+
+class CheckboxFormFieldOps(ChoicesFormFieldOps):
+
+    def _query(self, query):
+        if self.term.values:
+            subq = query.sub_expr('OR')
+            basename = self.colname()
+            for value in self.term.values:
+                subq.where('%s%s' % (self.colname(), value.lower()))
+
+
+class FieldOpsCache:
+
+    def __init__(self):
+        self.cache = {}
+
+    def demog_field_ops(self, field):
+        if field.name == 'DOB':
+            return [('range', DOBDemogFieldOps)]
+        if isinstance(field, demogfields.SelDemogField):
+            return [('in', InDemogFieldOps)]
+        if isinstance(field, demogfields.DatetimeBase):
+            return [('range', DateRangeDemogFieldOps)]
+        if field.name == 'case_id':
+            return [('range', RangeDemogFieldOps)]
+        if field.name in ('surname', 'given_names'):
+            return [
+                ('pattern', PatternDemogFieldOps),
+                ('phonetic', NamesDemogFieldOps),
+            ]
+        if field.name == 'deleted':
+            return [('select', DeletedDemogFieldOps)]
+        if field.name == 'tags':
+            return [('select', TagsDemogFieldOps)]
+        return [('pattern', PatternDemogFieldOps)]
+
+    form_cls_ops_map = {
+        form_ui.TextInput: [('pattern', PatternFormFieldOps)],
+        form_ui.IntInput: [('range', RangeFormFieldOps)],
+        form_ui.FloatInput: [('range', RangeFormFieldOps)],
+        form_ui.TextArea: [('pattern', PatternFormFieldOps)],
+        form_ui.DropList: [('in', ChoicesFormFieldOps)],
+        form_ui.RadioList: [('in', ChoicesFormFieldOps)],
+        form_ui.CheckBoxes: [('in', CheckboxFormFieldOps)],
+        form_ui.DateInput: [('range', DateRangeFormFieldOps)],
+        form_ui.TimeInput: [('range', RangeFormFieldOps)],
+        form_ui.DatetimeInput: [('range', DateTimeRangeFormFieldOps)],
+    }
+
+    def form_field_ops(self, input):
+        for inputbase in inspect.getmro(input.__class__):
+            if inputbase in self.form_cls_ops_map:
+                return self.form_cls_ops_map[inputbase]
+        return [('pattern', PatternFormFieldOps)]
+
+    def get_ops(self, params, field_name, form_name=None):
+        if form_name and form_name != 'demog':
+            info = params.form_info(form_name)
+            key = form_name, info.version, field_name
+            try:
+                ops = self.cache[key]
+            except KeyError:
+                input = info.load().columns.find_input(field_name)
+                ops = self.cache[key] = self.form_field_ops(input)
+        else:
+            key = field_name
+            try:
+                ops = self.cache[key]
+            except KeyError:
+                field = params.demog_fields().field_by_name(field_name)
+                ops = self.cache[key] = self.demog_field_ops(field)
+        return ops
+
+    def term_ops(self, term):
+        for op, ops_cls in self.get_ops(term.params, term.field, term.form):
+            if term.op == op:
+                return ops_cls(term)
+    
+    def available_ops(self, params, field, form):
+        return [op for op, ops_cls in self.get_ops(params, field, form)]
+
+field_ops_cache = FieldOpsCache()
+
+
+terms_by_op = {}
+
+class FilterMeta(type):
+
+    def __init__(cls, name, bases, dict):
+        if cls.op:
+            terms_by_op[cls.op.lower()] = cls
+
+
+def make_term(op, **kw):
+    try:
+        cls = terms_by_op[op.lower()]
+    except KeyError:
+        raise Error('Unknown filter operator %r' % op)
+    try:
+        return cls(**kw)
+    except Exception:
+        print cls
+        raise
+
+
+class FilterBase(object):
+
+    """
+    Public interface:
+        form                    group name (null for demog, form name for forms)
+        field                   field name
+        value                   match value for single-value matches
+        values                  match values for multi-match filters
+
+        form_label()            Group label (Demographics/form name)
+        label()                 Field label
+        get_markup()            Filter op markup name
+        options()               For discrete filters, value options
+        date_format()           User's preferred date fmt (date/time cols only)
+        parse()                 Convert filter value to py type
+        query(query)            Add this term to the given Query object
+        to_xml(xmlgen)          Add this term to the given XMLWriter object
+
+    Internal interface:
+        _attr_to_xml(xmlgen)    XML: Emit attrs
+        _value_to_xml(xmlgen)   XML: Emit value
+    """
+
+    __metaclass__ = FilterMeta
+    params = None
+    op = None
+
+    def desc(self):
+        return None
+
+    def _set_params(self, params):
+        self.params = params
+
+    def _forms_used(self, used):
+        pass
+
+    def _check(self, msgs):
+        return True
+
+    def _trim(self):
+        return 1
+
+    def to_xml(self):
+        raise NotImplementedError
+
+
+class TermBase(FilterBase):
+
+    field = None
+    form = None
+    value = None
+    negate = False
+    field_ops = None
+
+    def __init__(self, field, **kw):
+        self.field = field
+        self.__dict__.update(kw)
+
+    def _set_params(self, params):
+        if self.params is None:
+            self.params = params
+            self.field_ops = field_ops_cache.term_ops(self)
+
+    def get_markup(self):
+        return self.op
+
+    def deleted_filter(self):
+        return not self.form and self.field == 'deleted'
+
+    def date_format(self):
+        return self.field_ops.date_format()
+
+    def parse(self, v):
+        return self.field_ops.parse(v)
+
+    def options(self):
+        return self.field_ops.options()
+
+    def query(self, query):
+        return self.field_ops.query(query)
+
+    def label(self):
+        return self.field_ops.label()
+
+    def form_label(self):
+        return self.field_ops.form_label()
+
+    def allow_negate(self):
+        return self.field_ops.allow_negate
+
+    def _forms_used(self, used):
+        if self.form:
+            used.add(self.form)
+
+    def _check(self, msgs):
+        return self.field_ops.check(msgs)
+
+    def _attr_to_xml(self, node):
+        pass
+
+    def _value_to_xml(self, xmlgen):
+        if self.value:
+            v = xmlgen.push('value')
+            v.text(self.value)
+            xmlgen.pop()
+
+    def to_xml(self, xmlgen):
+        node = xmlgen.push('term')
+        if self.form:
+            node.attr('form', self.form)
+        node.attr('field', self.field)
+        node.attr('op', self.op)
+        if boolstr(self.negate):
+            node.attr('negate', 'yes')
+        self._attr_to_xml(node)
+        self._value_to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class PlaceholderTerm(TermBase):
+
+    op = 'placeholder'
+
+    def __init__(self):
+        pass
+
+    def _set_params(self, params):
+        pass
+
+    def label(self):
+        return ''
+
+    def form_label(self):
+        return ''
+
+    def desc(self):
+        return ' '
+
+    def _trim(self):
+        return 0
+
+    def to_xml(self, xmlgen):
+        pass
+
+    
+class PatternTerm(TermBase):
+    
+    op = 'pattern'
+
+    def desc(self):
+        if boolstr(self.negate):
+            return 'not matching pattern %r' % self.value
+        else:
+            return 'matching pattern %r' % self.value
+
+
+class PhoneticTerm(TermBase):
+    
+    op = 'phonetic'
+
+    def desc(self):
+        return 'phonetically matches %r' % self.value
+
+
+class RangeTerm(TermBase):
+
+    op = 'range'
+    from_value = ''
+    to_value = ''
+    incl_from = True
+    incl_to = False
+
+    def desc(self):
+        desc = []
+        if boolstr(self.negate):
+            desc.append('not')
+        if self.from_value:
+            desc.append('from %s' % self.from_value)
+            if self.incl_from:
+                desc.append('(inclusive)')
+        if self.to_value:
+            desc.append('to %s' % self.to_value)
+            if self.incl_to:
+                desc.append('(inclusive)')
+        return ' '.join(desc)
+
+    def _value_to_xml(self, xmlgen):
+        if self.from_value:
+            se = xmlgen.push('from')
+            if self.incl_from:
+                se.attr('inclusive', 'yes')
+            value = self.from_value
+            if self.field_ops and self.field_ops.iso_fmt:
+                value = self.parse(value).strftime(self.field_ops.iso_fmt)
+            se.text(value)
+            xmlgen.pop()
+        if self.to_value:
+            se = xmlgen.push('to')
+            value = self.to_value
+            if self.field_ops and self.field_ops.iso_fmt:
+                value = self.parse(value).strftime(self.field_ops.iso_fmt)
+            se.text(value)
+            if self.incl_to:
+                se.attr('inclusive', 'yes')
+            xmlgen.pop()
+
+
+class InTerm(TermBase):
+    
+    op = 'in'
+
+    def __init__(self, field, **kw):
+        self.values = []
+        TermBase.__init__(self, field, **kw)
+
+    def desc(self):
+        if len(self.values) == 1:
+            if boolstr(self.negate):
+                op = 'is not'
+            else:
+                op = 'is'
+        else:
+            if boolstr(self.negate):
+                op = 'is not in'
+            else:
+                op = 'is in'
+        label_map = dict(self.options())
+        values = [label_map.get(v, v) for v in self.values]
+        values.sort()
+        return '%s %s' % (op, ', '.join(values))
+
+    def get_markup(self):
+        if len(self.options()) > 10:
+            return 'multiselect'
+        else:
+            return 'checkboxes'
+
+    def _value_to_xml(self, xmlgen):
+        for value in self.values:
+            v = xmlgen.push('value')
+            v.text(value)
+            xmlgen.pop()
+
+
+class CasesetTerm(TermBase):
+    
+    op = 'caseset'
+
+    def __init__(self, case_ids=None, caseset=None, field='case_id', **kw):
+        TermBase.__init__(self, field, **kw)
+        if case_ids is None:
+            case_ids = []
+        self.values = case_ids
+        self.caseset = caseset
+
+    def _set_params(self, params):
+        if self.params is None:
+            self.params = params
+            self.field_ops = CasesetDemogFieldOps(self)
+
+    def desc(self):
+        return 'Case set: %s (%d cases)' % (self.caseset, len(self.values))
+
+    def _attr_to_xml(self, node):
+        node.attr('caseset', self.caseset)
+
+    def _value_to_xml(self, xmlgen):
+        e = xmlgen.push('commalist')
+        e.text(','.join([str(v) for v in self.values]))
+        xmlgen.pop()
+
+
+class SelectTerm(TermBase):
+
+    op = 'select'
+
+    def desc(self):
+        label_map = {}
+        for v, l in self.options():
+            label_map[str(v)] = l
+        return 'is %s' % label_map.get(str(self.value), self.value)
+
+
+class Conjunction(FilterBase):
+
+    def __init__(self):
+        self.children = []
+
+    def desc(self):
+        return self.op
+
+    def deleted_filter(self):
+        for child in self.children:
+            if child.deleted_filter():
+                return True
+        return False
+
+    def _set_params(self, params):
+        self.params = params
+        for child in self.children:
+            child._set_params(params)
+
+    def add_filter(self, filter):
+        if self.params is not None and self.params.syndrome_id is not None:
+            filter._set_params(self.params)
+        self.children.append(filter)
+
+    def del_filter(self, filter):
+        try:
+            self.children.remove(filter)
+            return True
+        except ValueError:
+            for child in self.children:
+                if hasattr(child, 'children') and child.del_filter(filter):
+                    if not child.children:
+                        self.children.remove(child)
+                    return True
+            return False
+
+    def toggle_conj(self):
+        if self.op == 'and':
+            self.op = 'or'
+        else:
+            self.op = 'and'
+
+    def _forms_used(self, used):
+        for child in self.children:
+            child._forms_used(used)
+
+    def _check(self, msgs):
+        okay_children = []
+        for child in self.children:
+            if not child._check(msgs):
+                continue
+            okay_children.append(child)
+        self.children = okay_children
+        return True
+
+    def _trim(self):
+        okay_children = []
+        size = 0
+        for child in self.children:
+            child_size = child._trim()
+            if child_size:
+                okay_children.append(child)
+                size += child_size
+        self.children = okay_children
+        return size
+
+    def query(self, query):
+        query = query.sub_expr(conjunction=self.op)
+        for child in self.children:
+            child.query(query)
+
+    def to_xml(self, xmlgen):
+        if self.children:
+            e = xmlgen.push('filter')
+            e.attr('op', self.op)
+            for child in self.children:
+                child.to_xml(xmlgen)
+            xmlgen.pop()
+
+
+class AndExpr(Conjunction):
+
+    op = 'and'
+
+
+class OrExpr(Conjunction):
+
+    op = 'or'
+
+
+class FilterGroup:
+
+    has_field = None
+
+    def __init__(self, params):
+        self.params = params
+
+    def available_filters(self):
+        filters = [(fl.lower(), fl, fn) 
+                   for fn, fl in self.filters()]
+        filters.sort()
+        filters = [(fn, fl) for fll, fl, fn in filters]
+        filters.insert(0, ('', '- Choose filter -'))
+        return filters
+
+
+class DemogFilterGroup(FilterGroup):
+
+    name = 'demog'
+    label = 'Demographics'
+    has_field = True
+
+    def filters(self):
+        demog_fields = self.params.demog_fields('report')
+        for field in demog_fields:
+            if field.name != 'case_definition':
+                yield field.name, field.label
+
+    def make_filter(self, adder):
+        return make_term(adder.op, field=adder.field)
+
+    def __repr__(self):
+        return '<DemogFilterGroup name=%r, label=%r>' %\
+            (self.name, self.label)
+
+
+class FormFilterGroup(FilterGroup):
+
+    has_field = True
+
+    def __init__(self, params, name, label):
+        FilterGroup.__init__(self, params)
+        self.name = name
+        self.label = label
+
+    def filters(self):
+        for input in self.params.form_info(self.name).load().get_inputs():
+            name = input.column.lower()
+            yield name, input.label or input.column
+
+    def make_filter(self, adder):
+        return make_term(adder.op, field=adder.field, form=adder.group)
+
+    def __repr__(self):
+        return '<FormFilterGroup name=%r, label=%r>' %\
+            (self.name, self.label)
+
+
+class AndSubexpressionGroup(FilterGroup):
+    
+    name = 'andsubexpr'
+    label = 'AND Subexpression'
+
+    def make_filter(self, adder):
+        return AndExpr()
+
+
+class OrSubexpressionGroup(FilterGroup):
+    
+    name = 'orsubexpr'
+    label = 'OR Subexpression'
+
+    def make_filter(self, adder):
+        return OrExpr()
+
+
+class FilterAdder(list):
+
+    def __init__(self, params, parent):
+        self.params = params
+        self.parent = parent
+        self.group = 'demog'
+        self.field = None
+        self.op = None
+        self.append(DemogFilterGroup(self.params))
+        self.append(AndSubexpressionGroup(self.params))
+        self.append(OrSubexpressionGroup(self.params))
+        for info in self.params.all_form_info():
+            self.append(FormFilterGroup(self.params, info.name, info.label))
+        self.group_map = dict([(group.name, group) for group in self])
+        self.placeholder = PlaceholderTerm()
+        if parent is not None:
+            self.parent.add_filter(self.placeholder)
+
+    def groups(self):
+        return [(group.name, group.label) for group in self]
+
+    def has_field(self):
+        group = self.group_map.get(self.group)
+        return group is not None and group.has_field
+
+    def fields(self):
+        return self.group_map.get(self.group).available_filters()
+
+    def field_ops(self):
+        return field_ops_cache.available_ops(self.params, 
+                                             self.field, self.group)
+
+    def abort(self):
+        self.parent.del_filter(self.placeholder)
+
+    def is_complete(self):
+        group = self.group_map.get(self.group)
+        if group is None:
+            return False
+        if not group.has_field:
+            return True
+        if not self.field:
+            return False
+        ops = self.field_ops()
+        if len(ops) < 2:
+            self.op = ops[0]
+            return True
+        return bool(self.op)
+
+    def add(self):
+        self.parent.del_filter(self.placeholder)
+        group = self.group_map.get(self.group)
+        filter = group.make_filter(self)
+        if filter is not None:
+            self.parent.add_filter(filter)
+        return filter
+
+
+def yield_nodes(root):
+    """
+    Yield a stream of events like:
+      'open', parent
+        'clause', child
+        'op', 'AND'
+        'open', child
+          'clause', childchild
+          'op', 'OR'
+          'clause', childchild
+        'close, child
+      'close', parent
+    """
+    class StackLevel:
+        def __init__(self, node):
+            self.node = node
+            self.index = 0
+            self.child_iter = iter(node.children)
+
+        def next_child(self):
+            try:
+                node = self.child_iter.next()
+            except StopIteration:
+                node = None
+            try:
+                return self.index, node
+            finally:
+                self.index += 1
+
+    stack = [StackLevel(root)]
+    path = [None]
+    while stack:
+        level = stack[-1]
+        index, child = level.next_child()
+        path[-1] = str(index)
+        levelpath = '.'.join(path[:-1])
+        if index == 0:
+            yield 'open', levelpath, level.node
+        if child is None:
+            yield 'close', levelpath, level.node
+            del stack[-1]
+            del path[-1]
+        else:
+            #if index != 0:
+            #    yield 'op', levelpath, level.node
+            if hasattr(child, 'children'):
+                stack.append(StackLevel(child))
+                path.append(None)
+            else:
+                yield 'clause', '.'.join(path), child
+
+
+class FilterParamsMixin:
+
+    show_filters = True
+
+    def init(self):
+        self.add_filter(AndExpr())
+
+    def walk_filters(self):
+        return yield_nodes(self.filter)
+
+    def path_filter(self, path):
+        node = self.filter
+        if path:
+            path = map(int, path.split('.'))
+            for index in path:
+                node = node.children[index]
+        return node
+
+    def make_term(self, op, **kw):
+        term = make_term(op, **kw)
+        term._set_params(self)
+        return term
+
+    def add_filter(self, filter):
+        filter._set_params(self)
+        self.filter = filter
+
+    def del_filter(self, filter):
+        return self.filter.del_filter(filter)
+
+    def filter_adder(self, parent):
+        return FilterAdder(self, parent)
+
+    def caseset_filter(self, case_ids, name):
+        self.filter.add_filter(CasesetTerm(case_ids, name))
+
+    def deleted_filter(self):
+        return self.filter.deleted_filter()
+
+    def filter_query(self, query, form_based=False):
+        if not self.filter._trim():
+            return
+        forms = set()
+        self.filter._forms_used(forms)
+        if forms:
+            if not form_based:
+                # This isolates the cartesian product effect of form joins
+                # from the case query, preventing duplication of cases.
+                query = query.in_select('cases.case_id', 'cases',
+                                    columns=['cases.case_id'])
+                query.join('JOIN persons USING (person_id)')
+                query.where('syndrome_id = %s', self.syndrome_id)
+            for form_name in forms:
+                table = self.form_info(form_name).tablename()
+                # We use a implicit view-join here to hide the indirection
+                # through the case_form_summary table, making the results of
+                # the join more intuitive.
+                query.join('LEFT JOIN (SELECT case_id, %s.* FROM %s'
+                            ' JOIN case_form_summary USING (summary_id)'
+                            ' WHERE NOT deleted)'
+                           ' AS %s USING (case_id)' % (table, table, table))
+        self.filter.query(query)
+
+    def _check(self, msgs):
+        self.filter._set_params(self)
+        self.filter._check(msgs)
+
+    def _forms_used(self, used):
+        self.filter._forms_used(used)
+
+    def _to_xml(self, xmlgen, curnode):
+        self.filter._trim()
+        self.filter.to_xml(xmlgen)
diff --git a/casemgr/reports/store.py b/casemgr/reports/store.py
new file mode 100644
index 0000000..93e1420
--- /dev/null
+++ b/casemgr/reports/store.py
@@ -0,0 +1,247 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from cocklebur import xmlwriter
+
+from casemgr import globals, cached, syndrome
+from casemgr.reports.common import *
+
+import config
+
+
+class ParamSaveMixin:
+
+    label = loaded_label = None
+    sharing = loaded_sharing = 'private'
+    loaded_from_id = None
+
+    sharing_options = [
+        ('private', 'None'),
+        ('unit', config.unit_label),
+        ('public', 'Public'),
+        ('quick', 'Quick'),
+    ]
+
+    def init(self):
+        self.loaded_from_id = None
+
+    def xmlsave(self, f):
+        xmlgen = xmlwriter.XMLwriter(f)
+        self.to_xml(xmlgen)
+
+    def _save(self, cred, row=None, sharing='private'):
+        if row is None:
+            row = globals.db.new_row('report_params')
+        row.label = self.label
+        row.syndrome_id = self.syndrome_id
+        row.type = self.report_type
+        row.sharing = sharing
+        row.user_id = row.unit_id = None
+        if sharing in ('private', 'last'):
+            row.user_id = cred.user.user_id
+        elif sharing == 'unit':
+            row.unit_id = cred.unit.unit_id
+        f = StringIO()
+        self.xmlsave(f)
+        row.xmldef = f.getvalue()
+        row.db_update()
+        self.loaded_from_id = row.report_params_id
+        self.loaded_label = row.label
+        if sharing == 'quick':
+            globals.notify.notify('report_quick', row.report_params_id)
+
+    def save(self, cred):
+        row = None
+        if self.loaded_from_id is not None and self.label == self.loaded_label:
+            query = globals.db.query('report_params')
+            query.where('report_params_id = %s', self.loaded_from_id)
+            row = query.fetchone()
+        sharing = self.sharing
+        if sharing == 'last':
+            sharing = 'private'
+        self._save(cred, row, sharing)
+
+    def autosave(self, cred):
+        # This should only save if the parameters have changed, but the current
+        # logic is not suited to detecting this.
+        query = globals.db.query('report_params')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        query.where('sharing = %s', 'last')
+        query.where('user_id = %s', cred.user.user_id)
+        rows = query.fetchall()
+        if rows:
+            row = rows[0]
+        else:
+            row = None
+        self._save(cred, row, 'last')
+
+
+def delete(report_params_id):
+    query = globals.db.query('report_params')
+    query.where('report_params_id = %s', report_params_id)
+    query.delete()
+    globals.notify.notify('report_params', report_params_id)
+
+
+legacy = (
+    ('report', 'reports.report'),
+    ('reportfilters', 'reports.reportfilters'),
+    ('reportcolumns', 'reports.reportcolumns'),
+    ('reportcrosstab', 'reports.crosstab'),
+)
+
+def _parse_file(f):
+    # Avoid import cycle
+    from casemgr.reports import xmlload
+    return xmlload.xmlload(f)
+
+
+def parse_file(syndrome_id, f):
+    params = _parse_file(f)
+    params.syndrome_id = syndrome_id
+    params.defaults()
+    return params
+
+
+def _decode(row):
+    # Avoid import cycle
+    from casemgr.reports import xmlload
+    try:
+        params = _parse_file(StringIO(row.xmldef))
+    except ReportParseError, e:
+        raise ReportLoadError, 'load %r: %s' % (row.label, e), sys.exc_info()[2]
+    params.syndrome_id = row.syndrome_id
+    params.label = params.loaded_label = row.label
+    params.sharing = params.loaded_sharing = row.sharing
+    params.loaded_from_id = row.report_params_id
+    params.defaults()
+    return params
+
+
+def load(report_params_id, cred):
+    query = globals.db.query('report_params')
+    query.where('report_params_id = %s', report_params_id)
+    row = query.fetchone()
+    if row is None:
+        raise Error('Report not found')
+    return _decode(row)
+
+
+def load_last(syndrome_id, report_type, cred):
+    """
+    Load the last report parameters the user was working on, or a
+    new parameter set if none were found.
+
+    NOTE - currently unusued (functionality subsumed into report menu)
+    """
+    query = globals.db.query('report_params')
+    query.where('label is null')
+    query.where('syndrome_id = %s', syndrome_id)
+    query.where('sharing = %s', 'last')
+    query.where('user_id = %s', cred.user.user_id)
+    query.where('type = %s', report_type)
+    row = query.fetchone()
+    if row is not None:
+        try:
+            return _decode(row)
+        except Error:
+            pass
+    return new_report(syndrome_id, report_type)
+
+
+class ReportMenuItem(object):
+
+    __slots__ = ('report_params_id', 'label', 'sharing', 'type', 
+                 'unit_id', 'user_id', 'sharing')
+
+    def __init__(self, attrs):
+        for col, value in zip(self.__slots__, attrs):
+            setattr(self, col, value)
+
+
+class ReportMenu(list):
+
+    def __init__(self, cred, syndrome_id, report_type=None):
+        self.syndrome_name = syndrome.syndromes[syndrome_id].name
+        self.by_sharing = dict([(mode, []) for mode in sharing_tags])
+        query = globals.db.query('report_params', order_by='label')
+        query.where('syndrome_id = %s', syndrome_id)
+        if report_type:
+            query.where('type = %s', report_type)
+        sub = query.sub_expr('OR')
+        sub.where("sharing IN ('quick', 'public')")
+        sub.where("(sharing = 'unit' AND unit_id = %s)", cred.unit.unit_id)
+        sub.where("(sharing IN ('private', 'last') AND user_id = %s)", 
+                    cred.user.user_id)
+        for row in query.fetchcols(ReportMenuItem.__slots__):
+            ur = ReportMenuItem(row)
+            self.append(ur)
+            if ur.sharing == 'last':
+                if ur.label:
+                    ur.label = 'Most recent: ' + ur.label
+                else:
+                    ur.label = 'Most recent'
+                self.by_sharing['private'].insert(0, ur)
+                continue
+            self.by_sharing[ur.sharing].append(ur)
+
+
+class ReportCache(object):
+
+    __slots__ = 'report_params_id', 'syndrome_id', 'label', 'unit_id', 'type'
+
+    def __init__(self, attrs):
+        for col, value in zip(self.__slots__, attrs):
+            setattr(self, col, value)
+
+
+class ReportsCache(cached.NotifyCache):
+
+    notification_target = 'report_quick'
+
+    def load(self):
+        self.by_synd_by_unit = {}
+        self.reports = []
+        query = globals.db.query('report_params', order_by='label')
+        # This machinery supports including per-unit reports on the main page,
+        # although this is currently disabled.
+        query.where('sharing = %s', 'quick')
+        for row in query.fetchcols(ReportCache.__slots__):
+            self.reports.append(ReportCache(row))
+
+    def get_synd_unit(self, syndrome_id, unit_id):
+        self.refresh()
+        key = syndrome_id, unit_id
+        try:
+            return self.by_synd_by_unit[key]
+        except KeyError:
+            by_synd_by_unit = [report
+                               for report in self.reports
+                               if (report.syndrome_id == syndrome_id)
+                                   and (report.unit_id is None 
+                                     or report.unit_id == unit_id)]
+            self.by_synd_by_unit[key] = by_synd_by_unit
+            return by_synd_by_unit
+
+reports_cache = ReportsCache()
diff --git a/casemgr/reports/xmlload.py b/casemgr/reports/xmlload.py
new file mode 100644
index 0000000..a921d43
--- /dev/null
+++ b/casemgr/reports/xmlload.py
@@ -0,0 +1,317 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+from cocklebur import xmlparse, utils
+
+from casemgr.reports import report, reportfilters
+from casemgr.reports.common import *
+
+class OrderingXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'ordering'
+
+    class OrderBy(xmlparse.Node):
+        permit_attrs = (
+            'column', 'direction',
+        )
+
+    class Ordering(xmlparse.Node):
+        __slots__ = 'params',
+        subtags = (
+            'orderby',
+        )
+        def start_element(self, parent):
+            self.params = parent.params
+
+        def end_child(self, child):
+            self.params.add_order(**child.attrs)
+
+
+class ColumnsXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'groups'
+
+    class Column(xmlparse.Node):
+        permit_attrs = (
+            'name', 'label', 
+        )
+
+    class Group(xmlparse.ContainerNode):
+        permit_attrs = (
+            'type', 'form', 'label', 
+        )
+        subtags = (
+            'column',
+        )
+        def end_element(self, parent):
+            group = parent.params.add_group(**self.attrs)
+            for child in self.children:
+                group.add(**child.attrs)
+
+    class Groups(xmlparse.Node):
+        __slots__ = 'params',
+        subtags = (
+            'group',
+        )
+        def start_element(self, parent):
+            self.params = parent.params
+
+
+
+class FiltersXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'filter'
+
+    class Value(xmlparse.Node):
+
+        def end_element(self, parent):
+            if parent.term.op == 'in':
+                parent.term.values.append(self.get_text())
+            else:
+                parent.term.value = self.get_text()
+
+    class Commalist(xmlparse.Node):
+
+        def end_element(self, parent):
+            parent.term.values = utils.commasplit(self.get_text())
+
+    class From(xmlparse.Node):
+        permit_attrs = (
+            'inclusive:bool', 
+        )
+        def end_element(self, parent):
+            parent.term.from_value = self.get_text()
+            if 'inclusive' in self.attrs:
+                parent.term.incl_from = self.attrs['inclusive']
+
+    class To(xmlparse.Node):
+        permit_attrs = (
+            'inclusive:bool', 
+        )
+        def end_element(self, parent):
+            parent.term.to_value = self.get_text()
+            if 'inclusive' in self.attrs:
+                parent.term.incl_to = self.attrs['inclusive']
+
+    class Term(xmlparse.Node):
+        __slots__ = 'term',
+        permit_attrs = (
+            'field', 'form', 'op', 'caseset', 'negate:bool', 'phonetic:bool',
+        )
+        subtags = (
+            'value', 'commalist', 'from', 'to',
+        )
+
+        def start_element(self, parent):
+            self.term = reportfilters.make_term(**self.attrs)
+            parent.add_filter(self.term)
+
+
+    class Filter(xmlparse.Node):
+        __slots__ = 'filter',
+        permit_attrs = (
+            'op',
+        )
+        subtags = (
+            'term', 'filter',
+        )
+        def start_element(self, parent):
+            op = self.attrs.get('op', 'and')
+            self.filter = reportfilters.make_term(op)
+            parent.add_filter(self.filter)
+
+        def add_filter(self, node):
+            self.filter.add_filter(node)
+
+
+class CrossTabXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'crosstab'
+
+    class Axis(xmlparse.Node):
+        permit_attrs = (
+            'name', 'type', 'form', 'field', 
+        )
+        def end_element(self, parent):
+            parent.params.set_axis(**self.attrs)
+
+    class CrossTab(xmlparse.Node):
+        __slots__ = 'params',
+        permit_attrs = (
+            'include_empty_rowsncols:bool', 'include_empty_pages:bool', 
+        )
+        subtags = (
+            'axis',
+        )
+        def start_element(self, parent):
+            self.params = parent.params
+
+        def end_element(self, parent):
+            parent.set('empty_rowsncols', 
+                self.attrs.get('include_empty_rowsncols'))
+            parent.set('empty_pages', 
+                self.attrs.get('include_empty_pages'))
+
+
+class EpiCurveXMLParse(xmlparse.XMLParse):
+        
+    root_tag = 'epicurve'
+
+    class Suppress(xmlparse.Node):
+
+        def end_element(self, parent):
+            parent.params.ts_stack_suppress.append(self.get_text())
+
+    class Dates(xmlparse.Node):
+        required_attrs = (
+            'field',
+        )
+        permit_attrs = (
+            'form', 
+        )
+        def start_element(self, parent):
+            form = self.attrs.get('form', '')
+            parent.params.set_join(form)
+            field = '%s:%s' % (form, self.attrs['field'])
+            if parent.params.ts_bincol:
+                parent.params.ts_bincol2 = field
+            else:
+                parent.params.ts_bincol = field
+
+    class Stacking(xmlparse.Node):
+        __slots__ = 'params',
+        required_attrs = (
+            'field',
+        )
+        permit_attrs = (
+            'form', 'ratios:bool',
+        )
+        subtags = (
+            'suppress',
+        )
+        def start_element(self, parent):
+            self.params = parent.params
+            form = self.attrs.get('form', '')
+            self.params.set_join(form)
+            self.params.ts_stacking = '%s:%s' % (form, self.attrs['field'])
+            self.params.ts_stack_ratios = self.attrs.get('ratios', False)
+            self.params.ts_stack_suppress = []
+
+    class EpiCurve(xmlparse.Node):
+        __slots__ = 'params',
+        permit_attrs = (
+            'join', 'format', 'nbins', 'missing_forms:bool', 
+        )
+        subtags = (
+            'dates', 'stacking',
+        )
+        def start_element(self, parent):
+            self.params = parent.params
+            self.params.ts_bincol = self.params.ts_bincol2 = ''
+            parent.set('ts_outfmt', self.attrs.get('format'))
+            parent.set('ts_nbins', self.attrs.get('nbins'))
+
+
+class ContactVisXMLParse(xmlparse.XMLParse):
+        
+    root_tag = 'contactvis'
+
+    class ContactVis(xmlparse.Node):
+        permit_attrs = (
+            'format', 'labelwith', 'vismode', 
+        )
+        def end_element(self, parent):
+            parent.set('outputtype', self.attrs.get('format'))
+            parent.set('labelwith', self.attrs.get('labelwith'))
+            parent.set('vismode', self.attrs.get('vismode'))
+
+
+class ReportXMLParse(ColumnsXMLParse,
+                     FiltersXMLParse,
+                     OrderingXMLParse, 
+                     CrossTabXMLParse,
+                     EpiCurveXMLParse,
+                     ContactVisXMLParse,
+                     xmlparse.XMLParse):
+
+    root_tag = 'report'
+
+    class Header(xmlparse.SetAttrNode):
+        attr = 'header'
+
+    class Syndrome(xmlparse.SetAttrNode):
+        attr = 'syndrome'
+
+    class Preamble(xmlparse.SetAttrNode):
+        attr = 'preamble'
+
+    class Footer(xmlparse.SetAttrNode):
+        attr = 'footer'
+
+    class Export(xmlparse.Node):
+        permit_attrs = (
+            'strip_newlines:bool', 'column_labels', 'row_type', 
+        )
+        def end_element(self, parent):
+            parent.set('export_strip_newlines', 
+                                        str(self.attrs.get('strip_newlines')))
+            parent.set('export_column_labels', self.attrs.get('column_labels'))
+            parent.set('export_row_type', self.attrs.get('row_type'))
+
+    class FormDep(xmlparse.Node):
+        permit_attrs = (
+            'name', 'version:int', 'label',
+        )
+        def end_element(self, parent):
+            parent.params.saved_formdeps.append(self.attrs)
+
+    class Report(xmlparse.Node):
+        __slots__ = 'params',
+        required_attrs = (
+            'type', 'name',
+        )
+        subtags = (
+            'ordering', 'header', 'syndrome', 'preamble', 'footer', 'export',
+            'contactvis', 'crosstab', 'epicurve', 'groups', 'filter',
+            'formdep',
+        )
+        def set(self, attr, value):
+            if value is not None:
+                setattr(self.params, attr, value)
+
+        def add_filter(self, node):
+            self.params.add_filter(node)
+
+        def start_element(self, parent):
+            # XXX syndrome_id
+            ctor = report.get_report_ctor(self.attrs['type'])
+            self.params = ctor(None)
+
+        def end_element(self, parent):
+            self.params.label = self.attrs['name']
+            self.set('header', self.attrs.get('header'))
+            self.set('preamble', self.attrs.get('preamble'))
+            self.set('footer', self.attrs.get('footer'))
+
+
+def xmlload(f):
+    try:
+        return ReportXMLParse().parse(f).params
+    except xmlparse.ParseError, e:
+        raise ReportParseError, e, sys.exc_info()[2]
diff --git a/casemgr/resultpersons.py b/casemgr/resultpersons.py
new file mode 100644
index 0000000..f90813c
--- /dev/null
+++ b/casemgr/resultpersons.py
@@ -0,0 +1,182 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Code to support a person-oriented view of cases, as used by the search
+result pages.
+"""
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from casemgr import globals, paged_search, person, caseaccess, \
+                    demogfields, casetags
+
+class CaseSummary:
+    """
+    A wrapper around a case row object that presents a controllable
+    summary of the row.
+    """
+
+    def __init__(self, rp, case_row):
+        self.rp = rp
+        self.case_row = case_row
+        self.case_id = case_row.case_id
+        self.syndrome_id = case_row.syndrome_id
+
+    def summary(self):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        fields = fields.reordered_context_fields(self.rp.initial_cols, 'result')
+        return fields.summary(self.case_row)
+
+    def button(self, edit_case):
+        return self.rp.search_ops.button_case(self.case_id, self.syndrome_id)
+
+
+class Person(person.Person):
+
+    def __init__(self, rp, seed_person):
+        self.from_person(seed_person)
+        self.rp = rp
+        self.cases = []
+
+    def summary(self):
+        return person.Person.summary(self, self.rp.initial_cols)
+
+    def button(self, case):
+        return self.rp.search_ops.button_person(self.person_id)
+
+
+class ResultPersons(paged_search.PagedSearch):
+    result_type = 'person'
+
+    def __init__(self, search_ops, query, initial_cols=None, description=None):
+        paged_search.PagedSearch.__init__(self, globals.db, search_ops.prefs, 
+                                          'persons')
+        self.search_ops = search_ops
+        self.initial_cols = initial_cols
+        self.description = description
+        self.results_per_page = self.search_ops.prefs.get('persons_per_page')
+        self.fetch_pkeys(query)
+        # Can't use page_search.PagerSelect logic, because we're interested in
+        # case_ids, not person_ids.
+        self.selected = set()
+        self.page_case_ids = set()
+        self.page_selected = []
+
+    def single_case(self):
+        """
+        If search returned only one case, return the case_id
+        """
+        if len(self.pkeys) == 1:
+            person_id, case_ids = self.pkeys[0]
+            if len(case_ids) == 1:
+                return case_ids[0]
+
+    def fetch_pkeys(self, query):
+        keys = []
+        persons_keys = {}
+        for person_id, case_id in query.fetchcols(('person_id', 'case_id')):
+            person_keys = persons_keys.get(person_id)
+            if person_keys is None:
+                person_keys = persons_keys[person_id] = [case_id]
+                keys.append((person_id, person_keys))
+            else:
+                person_keys.append(case_id)
+        self.pkeys = keys
+
+    def new_button(self):
+        return self.search_ops.button_new()
+
+    def edit_button(self):
+        return self.search_ops.button_edit()
+
+    def _page_to_selected(self):
+        self.selected.difference_update(self.page_case_ids)
+        for key in self.page_selected:
+            self.selected.add(int(key))
+
+    def _selected_to_page(self):
+        self.page_selected = list(self.page_case_ids & self.selected)
+
+    def select(self, select):
+        self.selected = set(select)
+        self._selected_to_page()
+
+    def select_all(self):
+        case_ids = set()
+        for person_id, person_case_ids in self.pkeys:
+            case_ids.update(person_case_ids)
+        self.select(case_ids)
+
+    def get_selected(self):
+        # Yield selected cases in search order
+        self._page_to_selected()
+        selected = []
+        for person_id, person_case_ids in self.pkeys:
+            for case_id in person_case_ids:
+                if case_id in self.selected:
+                    selected.append(case_id)
+        return selected
+
+    def page_rows(self):
+        if not self.pkeys:
+            return []
+        # Collect ordering and linking information
+        self._page_to_selected()
+        person_ids = []
+        self.page_case_ids = set()
+        for person_id, person_case_ids in self.page_pkeys():
+            person_ids.append(person_id)
+            self.page_case_ids.update(person_case_ids)
+        self._selected_to_page()
+        # Fetch persons
+        person_map = {}
+        query = globals.db.query('persons')
+        query.where_in('person_id', person_ids)
+        for row in query.fetchall():
+            person_map[row.person_id] = Person(self, row)
+        # Fetch cases
+        case_map = {}
+        query = globals.db.query('cases')
+        query.where_in('case_id', self.page_case_ids)
+        for row in query.fetchall():
+            case_map[row.case_id] = CaseSummary(self, row)
+        # Fetch case tags 
+        cases_tags = casetags.CasesTags(self.page_case_ids)
+        # Now collate
+        result = []
+        for person_id, person_case_ids in self.page_pkeys():
+            try:
+                person = person_map[person_id]
+            except KeyError:
+                pass
+            else:
+                result.append(person)
+                for case_id in person_case_ids:
+                    try:
+                        case_summary = case_map[case_id]
+                    except KeyError:
+                        pass
+                    else:
+                        person.cases.append(case_summary)
+                        case_summary.case_row.tags = \
+                            cases_tags.get(case_summary.case_id)
+        return result
diff --git a/casemgr/rights.py b/casemgr/rights.py
new file mode 100644
index 0000000..fd06099
--- /dev/null
+++ b/casemgr/rights.py
@@ -0,0 +1,240 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+import config
+
+class RightDef:
+
+    def __init__(self, right, label, desc=None):
+        self.right = right
+        self.label = label
+        self.desc = desc
+
+class AvailableRights(list):
+
+    def __init__(self, *defs):
+        self.by_right = {}
+        self.options = []
+
+    def add(self, *args):
+        rightdef = RightDef(*args)
+        self.append(rightdef)
+        self.options.append((rightdef.right, rightdef.label))
+        self.by_right[rightdef.right] = rightdef
+
+    def __getitem__(self, right):
+        return self.by_right[right]
+
+    def __contains__(self, right):
+        return right in self.by_right
+
+
+available = AvailableRights()
+available.add('ADMIN', 'System Administrator'),
+available.add('DATAMGR', 'Data Administrator',
+             '%s merging' % config.person_label),
+available.add('UNITADMIN', 
+             'Administer users in the same %s' % config.unit_label.lower()),
+if config.user_registration_mode in ('invite', 'sponsor'):
+    available.add('SPONSOR', 'Sponsor new users'),
+available.add('ACCESSALL', 'Access all records'),
+available.add('ACCESSSYND', 'Access by %s' % config.syndrome_label),
+available.add('IMPORT', 'Bulk data import'),
+available.add('EXPORT', 'Bulk data export and reporting'),
+available.add('PUBREP', 'View-only access to public reports'),
+available.add('TQADMIN', 'Administer task queues'),
+available.add('VIEWONLY', 'View-Only Case Access'),
+available.add('TASKINIT', 'Create new tasks'),
+
+
+class Rights(set):
+    """
+    Set-like rights container.
+
+    Can be initialised from a comma-separated list of rights tags or
+    another Rights instance and supports the usual set membership,
+    union and intersection operations.
+    """
+    available = available
+ 
+    def __init__(self, rights=None):
+        set.__init__(self)
+        self.add(rights)
+ 
+    def __str__(self):
+        return ','.join(self)
+ 
+    def add(self, rights):
+        if rights:
+            try:
+                rights = rights.split(',')
+            except AttributeError:
+                pass
+            for right in rights:
+                set.add(self, right)
+
+    def any(self, *want):
+        return bool(self & set(want))
+
+
+class RMUser:
+
+    def __init__(self, id, name=None, fullname=None):
+        self.id = id
+        self.name = name
+        self.fullname = fullname
+
+    def dump(self):
+        print '      User', self.id, '-', self.name, '-', self.fullname
+
+
+class RMUnit:
+
+    def __init__(self, id, name=None):
+        self.id = id
+        self.name = name
+        self.users = {}
+
+    def sorted_users(self):
+        return asu(self.users.itervalues())
+
+    def dump(self):
+        print '    Unit', self.id, '-', self.name
+        for user in self.sorted_users():
+            user.dump()
+
+
+class RMGroup:
+
+    def __init__(self, id, name=None):
+        self.id = id
+        self.name = name
+        self.units = {}
+
+    def sorted_units(self):
+        return asu(self.units.itervalues())
+
+    def dump(self):
+        print '  Group', self.id, '-', self.name
+        for unit in self.sorted_units():
+            unit.dump()
+
+
+def asu(l):
+    al = [(o.name and o.name.lower(), o) for o in l]
+    al.sort()
+    return [o for n, o in al]
+
+
+class RightMembers:
+    """
+    Given a /right/, determine which users, units and groups have that right
+    """
+
+    def __init__(self, db, right):
+        self.right = right
+        self.label = available[right].label
+        self.group_by_id = {}
+        self.unit_by_id = {}
+        self.user_by_id = {}
+        self.direct_groups = []
+        self.direct_units = []
+        self.direct_users = []
+
+        unit_groups = {}
+        user_units = {}
+
+        # Scan groups for groups with /right/
+        query = db.query('groups')
+        cols = 'group_id', 'rights', 'group_name'
+        for group_id, rights, name in query.fetchcols(cols):
+            rights = Rights(rights)
+            if right in rights:
+                group = RMGroup(group_id, name)
+                self.group_by_id[group_id] = group
+                self.direct_groups.append(group)
+
+        # For groups of interest, determine membership
+        query = db.query('unit_groups')
+        query.where_in('group_id', self.group_by_id)
+        for group_id, unit_id in query.fetchcols(('group_id', 'unit_id')):
+            unit_groups.setdefault(unit_id, []).append(group_id)
+
+        # Scan units for units with /right/ (directly or via group)
+        query = db.query('units')
+        query.where('enabled')
+        cols = 'unit_id', 'rights', 'name'
+        for unit_id, rights, name in query.fetchcols(cols):
+            rights = Rights(rights)
+            groups = unit_groups.get(unit_id)
+            if right in rights or groups:
+                unit = RMUnit(unit_id, name)
+                self.unit_by_id[unit_id] = unit
+                if groups:
+                    for group_id in groups:
+                        self.group_by_id[group_id].units[unit_id] = unit
+                if right in rights:
+                    self.direct_units.append(unit)
+
+        # For units of interest, determine membership
+        query = db.query('unit_users')
+        query.where_in('unit_id', self.unit_by_id)
+        for unit_id, user_id in query.fetchcols(('unit_id', 'user_id')):
+            user_units.setdefault(user_id, []).append(unit_id)
+
+        # Scan users for users with /right/ (directly or via unit)
+        query = db.query('users')
+        query.where('(NOT deleted AND enabled)')
+        cols = 'user_id', 'rights', 'username', 'fullname'
+        for user_id, rights, name, fullname in query.fetchcols(cols):
+            rights = Rights(rights)
+            units = user_units.get(user_id)
+            if right in rights or units:
+                user = RMUser(user_id, name, fullname)
+                self.user_by_id[user_id] = user
+                if units:
+                    for unit_id in units:
+                        self.unit_by_id[unit_id].users[user_id] = user
+                if right in rights:
+                    self.direct_users.append(user)
+
+    def sorted_groups(self):
+        return asu(self.direct_groups)
+
+    def sorted_units(self):
+        return asu(self.direct_units)
+
+    def sorted_users(self):
+        return asu(self.direct_users)
+
+    def dump(self):
+        print 'Right', self.right, '-', self.label
+        print 'Direct groups'
+        for group in self.sorted_groups():
+            group.dump()
+        print 'Direct units'
+        for unit in self.sorted_units():
+            unit.dump()
+        print 'Direct users'
+        for user in self.sorted_users():
+            user.dump()
diff --git a/casemgr/schema/__init__.py b/casemgr/schema/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/casemgr/schema/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/casemgr/schema/check.py b/casemgr/schema/check.py
new file mode 100644
index 0000000..ae377f6
--- /dev/null
+++ b/casemgr/schema/check.py
@@ -0,0 +1,92 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import form_ui
+from casemgr.formutils import deploy
+
+def check_form_dependancies(db, db_user):
+    def set_from_query(query, *cols):
+        s = set()
+        for label, version in query.fetchcols(cols):
+            if version is None:
+                version = 0
+            s.add((label, version))
+        return s
+    # Check that the tables have been created, as are called prior to schema
+    # creation for new installs.
+    for table in ('form_defs', 'case_form_summary', 'syndrome_forms', 'forms'):
+        if not db.db_has_relation(table):
+            return
+    formlib = form_ui.FormLibXMLDB(db, 'form_defs')
+    # collect forms defined
+    defined = set()
+    for entry in formlib:
+        defined.add((entry.name, entry.version))
+    # collect "in-use" forms (referenced by summary instances)
+    query = db.query('case_form_summary', distinct=True)
+    in_use = set_from_query(query, 'form_label', 'form_version')
+    # collect "deployed" forms (cur_version not null or used by a syndrome)
+    query = db.query('syndrome_forms', distinct=True)
+    query.join('RIGHT JOIN forms ON (forms.label = syndrome_forms.form_label)')
+    query.where('syndrome_id IS NOT NULL')
+    deployed = set_from_query(query, 'label', 'cur_version')
+    # collect "registered" forms
+    registered = set(db.query('forms').fetchcols('label'))
+
+    # Warn for in-use, but missing defs
+    for missing in in_use - defined:
+        print '    *** WARNING - definition for in-use form %r vers %s is missing!' % missing
+    # Warn for deployed, but missing defs
+    for missing in deployed - in_use - defined:
+        print '    *** WARNING - definition for deployed form %r vers %s is missing!' % missing
+
+    # Create missing form instance tables
+    for name, version in (in_use | deployed) & defined:
+        table = formlib.tablename(name, version)
+        if not db.db_has_relation(table):
+            print '    *** form %r vers %s instance table is missing - creating' % (name, version)
+        elif db.has_table(table):
+            # Has table and describer - nothing further to do
+            continue
+        try:
+            form = formlib.load(name, version)
+        except form_ui.FormError, e:
+            print '    *** WARNING - unable to load %r, vers %s - FORM '\
+                  'INSTANCE TABLE NOT CREATED!' % (name, version)
+            print '        %s' % e
+        deploy.make_form_table(db, form, table)
+    # Create entries in "forms" table for defined forms
+    highest_by_name = {}
+    for name, version in defined:
+        if name not in registered:
+            if name not in highest_by_name or version > highest_by_name[name]:
+                highest_by_name[name] = version
+    for name, version in highest_by_name.items():
+        print '    *** noting new form %r' % name
+        form = formlib.load(name, version)
+        formrow = db.new_row('forms')
+        formrow.label = name
+        formrow.name = form.text
+        formrow.form_type = form.form_type
+        formrow.allow_multiple = form.allow_multiple
+        formrow.db_update()
diff --git a/casemgr/schema/report_1_6/__init__.py b/casemgr/schema/report_1_6/__init__.py
new file mode 100644
index 0000000..d556823
--- /dev/null
+++ b/casemgr/schema/report_1_6/__init__.py
@@ -0,0 +1,23 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+###############################################################################
+# NOTE - these are dummy implementations of the report classes to allow pre-1.7
+# report pickles to be loaded and converted to XML.
+###############################################################################
+
diff --git a/casemgr/schema/report_1_6/convert.py b/casemgr/schema/report_1_6/convert.py
new file mode 100644
index 0000000..ec264e4
--- /dev/null
+++ b/casemgr/schema/report_1_6/convert.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+import cPickle
+import base64
+import traceback
+from cStringIO import StringIO
+
+def report_cvt(db):
+    # Preload the report compatibility modules, and remap the module namespace
+    # so the unpickling can find them.
+    from casemgr.schema.report_1_6 import reportfilters, reportcrosstab,\
+                                          reportcolumns, report
+    sys.modules['casemgr.reportfilters'] = reportfilters
+    sys.modules['casemgr.reportcrosstab'] = reportcrosstab
+    sys.modules['casemgr.reportcolumns'] = reportcolumns
+    sys.modules['casemgr.report'] = report
+    try:
+        form_info = {}
+        query = db.query('forms')
+        for n, l, v in query.fetchcols(('label', 'name', 'cur_version')):
+            form_info[n.lower()] = (n, l, v)
+        query = db.query('report_params')
+        fail_count = 0
+        for row in query.fetchall():
+            if not row.pickle:
+                continue
+            try:
+                f = StringIO()
+                params = cPickle.loads(base64.decodestring(row.pickle))
+                params.xmlsave(f, form_info)
+                row.xmldef = f.getvalue()
+                if row.label is None and row.user_id is not None:
+                    row.sharing = 'last'
+                elif row.user_id is not None:
+                    row.sharing = 'private'
+                elif row.unit_id is not None:
+                    row.sharing = 'unit'
+                else:
+                    row.sharing = 'public'
+                row.db_update()
+            except Exception:
+                fail_count += 1
+                print >> sys.stderr, '*** Unable to convert report "%s"\n    (user %s, unit %s, sharing %s, type %s)' % (row.label, row.user_id, row.unit_id, row.sharing, row.type)
+                traceback.print_exc()
+        if fail_count:
+            sys.exit('%s reports were not converted' % fail_count)
+    finally:
+        for n, m in sys.modules.items():
+            if 'report_1_6' in getattr(m, '__file__', ''):
+                del sys.modules[n]
diff --git a/casemgr/schema/report_1_6/report.py b/casemgr/schema/report_1_6/report.py
new file mode 100644
index 0000000..43a43f9
--- /dev/null
+++ b/casemgr/schema/report_1_6/report.py
@@ -0,0 +1,97 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+###############################################################################
+# NOTE - these are dummy implementations of the report classes to allow pre-1.7
+# report pickles to be loaded and converted to XML.
+###############################################################################
+
+from cocklebur import introspect, xmlwriter
+
+import reportfilters, reportcolumns, reportcrosstab
+
+
+class OrderParam:
+
+    pass
+
+
+class OrderbyParamsMixin:
+
+    def _to_xml(self, xmlgen, curnode):
+        if self.order_by:
+            xmlgen.push('ordering')
+            for ob in self.order_by:
+                e = xmlgen.push('orderby')
+                e.attr('column', ob.col)
+                e.attr('direction', ob.rev)
+                xmlgen.pop()
+            xmlgen.pop()
+
+
+
+class ReportParamsBase:
+
+    report_type = None
+    label = None
+
+    def forms_used(self):
+        used = set()
+        introspect.callall(self, '_forms_used', used)
+        return used
+
+    def form_deps(self, xmlgen, form_info):
+        for form in self.forms_used():
+            name, label, version = form_info[form.lower()]
+            e = xmlgen.push('formdep')
+            e.attr('name', name)
+            e.attr('version', version)
+            e.attr('label', label)
+            xmlgen.pop()
+
+    def to_xml(self, xmlgen, form_info):
+        curnode = xmlgen.push('report')
+        curnode.attr('type', self.report_type)
+        curnode.attr('name', self.label)
+        xmlgen.pushtext('header', self.header)
+        self.form_deps(xmlgen, form_info)
+        introspect.callall(self, '_to_xml', xmlgen, curnode)
+        xmlgen.pop()
+
+    def xmlsave(self, f, form_info):
+        xmlgen = xmlwriter.XMLwriter(f)
+        self.to_xml(xmlgen, form_info)
+
+
+class LineReportParams(
+                reportfilters.FilterParamsMixin, 
+                reportcolumns.OutcolsParamsMixin, 
+                OrderbyParamsMixin,
+                ReportParamsBase):
+
+    report_type = 'line'
+
+ReportParams = LineReportParams
+
+
+class CrosstabReportParams(
+                reportcrosstab.CrosstabParams,
+                reportfilters.FilterParamsMixin,
+                ReportParamsBase):
+
+    report_type = 'crosstab'
diff --git a/casemgr/schema/report_1_6/reportcolumns.py b/casemgr/schema/report_1_6/reportcolumns.py
new file mode 100644
index 0000000..141890b
--- /dev/null
+++ b/casemgr/schema/report_1_6/reportcolumns.py
@@ -0,0 +1,72 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+###############################################################################
+# NOTE - these are dummy implementations of the report classes to allow pre-1.7
+# report pickles to be loaded and converted to XML.
+###############################################################################
+
+class ReportCol:
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('column')
+        e.attr('name', self.name)
+        e.attr('label', self.label)
+        xmlgen.pop()
+    
+
+class ReportColsBase(list):
+
+    form_name = None
+
+
+class DemogCols(ReportColsBase):
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('group')
+        e.attr('type', 'demog')
+        e.attr('label', self.label)
+        for c in self:
+            c.to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class FormCols(ReportColsBase):
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('group')
+        e.attr('type', 'form')
+        e.attr('form', self.form_name)
+        e.attr('label', self.label)
+        for c in self:
+            c.to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class OutcolsParamsMixin:
+
+    def _forms_used(self, used):
+        for outgroup in self.outgroups:
+            if outgroup.form_name:
+                used.add(outgroup.form_name)
+
+    def _to_xml(self, xmlgen, curnode):
+        xmlgen.push('groups')
+        for outgroup in self.outgroups:
+            outgroup.to_xml(xmlgen)
+        xmlgen.pop()
diff --git a/casemgr/schema/report_1_6/reportcrosstab.py b/casemgr/schema/report_1_6/reportcrosstab.py
new file mode 100644
index 0000000..d43d55e
--- /dev/null
+++ b/casemgr/schema/report_1_6/reportcrosstab.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+###############################################################################
+# NOTE - these are dummy implementations of the report classes to allow pre-1.7
+# report pickles to be loaded and converted to XML.
+###############################################################################
+
+class CrosstabAxisParams:
+
+    def to_xml(self, xmlgen, name):
+        e = xmlgen.push('axis')
+        e.attr('name', name)
+        if self.form_name:
+            e.attr('type', 'form')
+            e.attr('form', self.form_name)
+        else:
+            e.attr('type', 'demog')
+        e.attr('field', self.field)
+        xmlgen.pop()
+
+
+class CrosstabParams:
+
+    def _forms_used(self, used):
+        for axis in (self.row, self.col):
+            if axis.form_name:
+                used.add(axis.form_name)
+
+    def _to_xml(self, xmlgen, curnode):
+        e = xmlgen.push('crosstab')
+        self.row.to_xml(xmlgen, 'row')
+        self.col.to_xml(xmlgen, 'column')
+        xmlgen.pop()
diff --git a/casemgr/schema/report_1_6/reportfilters.py b/casemgr/schema/report_1_6/reportfilters.py
new file mode 100644
index 0000000..241aa1d
--- /dev/null
+++ b/casemgr/schema/report_1_6/reportfilters.py
@@ -0,0 +1,250 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+###############################################################################
+# NOTE - these are dummy implementations of the report classes to allow pre-1.7
+# report pickles to be loaded and converted to XML.
+###############################################################################
+
+from cocklebur import datetime
+
+class DemogFilterBase(object):
+
+    value = None
+    column = None
+    negate = False
+    op = None
+
+    def attr_to_xml(self, node):
+        pass
+
+    def value_to_xml(self, xmlgen):
+        v = xmlgen.push('value')
+        v.text(self.value)
+        xmlgen.pop()
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('term')
+        e.attr('field', self.name)
+        e.attr('op', self.op)
+        if self.negate:
+            e.attr('negate', 'yes')
+        self.attr_to_xml(e)
+        self.value_to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class PatternDemogFilter(DemogFilterBase):
+
+    op = 'pattern'
+
+
+class RangeFilterMixin(object):
+
+    op = 'range'
+    from_value = ''
+    to_value = ''
+    incl_from = True
+    incl_to = False
+    iso_fmt = None
+
+    def parse(self, v):
+        return v
+
+    def value_to_xml(self, xmlgen):
+        if self.from_value:
+            se = xmlgen.push('from')
+            if self.incl_from:
+                se.attr('inclusive', 'yes')
+            value = self.from_value
+            if self.iso_fmt:
+                value = self.parse(value).strftime(self.iso_fmt)
+            se.text(value)
+            xmlgen.pop()
+        if self.to_value:
+            se = xmlgen.push('to')
+            value = self.to_value
+            if self.iso_fmt:
+                value = self.parse(value).strftime(self.iso_fmt)
+            se.text(value)
+            if self.incl_to:
+                se.attr('inclusive', 'yes')
+            xmlgen.pop()
+
+
+class DateRangeFilterMixin(RangeFilterMixin):
+
+    iso_fmt = '%Y-%m-%d %H:%M:%S'
+
+    def parse(self, v):
+        try:
+            return datetime.parse_discrete(v, past=True).mx()
+        except datetime.Error, e:
+            raise Error(str(e))
+
+
+class RangeDemogFilter(RangeFilterMixin, DemogFilterBase):
+
+    pass
+
+
+class DateRangeDemogFilter(DateRangeFilterMixin, DemogFilterBase):
+
+    pass
+
+
+class DOBDemogFilter(DateRangeDemogFilter):
+
+    pass
+
+
+class InDemogFilter(DemogFilterBase):
+
+    op = 'in'
+
+    value = []
+
+    def value_to_xml(self, xmlgen):
+        for value in self.value:
+            v = xmlgen.push('value')
+            v.text(value)
+            xmlgen.pop()
+
+
+class CommalistDemogFilter(DemogFilterBase):
+    
+    op = 'pattern'
+
+
+class PhoneticDemogFilter(PatternDemogFilter):
+
+    phonetic = True
+
+    def to_xml(self, xmlgen):
+        if str(self.phonetic) == 'True':
+            self.op = 'phonetic'
+        else:
+            self.op = 'pattern'
+        PatternDemogFilter.to_xml(self, xmlgen)
+
+
+class DeletedDemogFilter(DemogFilterBase):
+
+    op = 'select'
+    value = 'False'
+
+    fixup_map = {
+        'True': 'exclude',
+        '': 'both',
+        'False': 'only',
+    }
+
+    def to_xml(self, xmlgen):
+        self.value = self.fixup_map.get(self.value, self.value)
+        DemogFilterBase.to_xml(self, xmlgen)
+
+
+class TagsDemogFilter(DemogFilterBase):
+
+    op = 'in'
+
+    def value_to_xml(self, xmlgen):
+        if self.value:
+            for value in self.value.split(','):
+                value = value.strip()
+                if value:
+                    v = xmlgen.push('value')
+                    v.text(value)
+                    xmlgen.pop()
+
+
+class FormFilterBase:
+
+    negate = False
+
+    def attr_to_xml(self, node):
+        pass
+
+    def value_to_xml(self, xmlgen):
+        v = xmlgen.push('value')
+        v.text(self.value)
+        xmlgen.pop()
+
+    def to_xml(self, xmlgen):
+        e = xmlgen.push('term')
+        e.attr('form', self.group)
+        e.attr('field', self.name)
+        e.attr('op', self.op)
+        if self.negate:
+            e.attr('negate', 'yes')
+        self.attr_to_xml(e)
+        self.value_to_xml(xmlgen)
+        xmlgen.pop()
+
+
+class PatternFormFilter(FormFilterBase):
+
+    op = 'pattern'
+
+
+class RangeFormFilter(RangeFilterMixin, FormFilterBase):
+
+    pass
+
+
+class DateRangeFormFilter(DateRangeFilterMixin, FormFilterBase):
+
+    iso_fmt = '%Y-%m-%d'
+
+
+class DateTimeRangeFormFilter(DateRangeFilterMixin, FormFilterBase):
+
+    pass
+
+
+class ChoicesFormFilter(FormFilterBase):
+
+    op = 'in'
+    value = []
+
+    def value_to_xml(self, xmlgen):
+        for value in self.value:
+            v = xmlgen.push('value')
+            v.text(value)
+            xmlgen.pop()
+
+
+class CheckboxFormFilter(ChoicesFormFilter):
+
+    pass
+
+
+class FilterParamsMixin:
+
+    def _forms_used(self, used):
+        for filter in self.filters:
+            if isinstance(filter, FormFilterBase):
+                used.add(filter.group)
+
+    def _to_xml(self, xmlgen, curnode):
+        if self.filters:
+            e = xmlgen.push('filter')
+            e.attr('op', 'AND')
+            for filter in self.filters:
+                filter.to_xml(xmlgen)
+            xmlgen.pop()
diff --git a/casemgr/schema/schema.py b/casemgr/schema/schema.py
new file mode 100644
index 0000000..32a5b39
--- /dev/null
+++ b/casemgr/schema/schema.py
@@ -0,0 +1,503 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.dbobj import *
+
+class Form(ResultRow):
+    types = [
+        ('case', 'Case'),
+        ('followup', 'Contact Followup'),
+    ]
+
+def define_db(dsn):
+    db = DatabaseDescriber(dsn)
+
+    td = db.new_table('syndrome_types')
+    td.column('syndrome_id', SerialColumn, primary_key = True)
+    td.column('priority', IntColumn)
+    td.column('name', StringColumn)
+    td.column('description', StringColumn)
+    td.column('additional_info', StringColumn)
+    td.column('enabled', BooleanColumn, default = 'False')
+    td.column('post_date', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.column('expiry_date', DatetimeColumn)
+    td.order_by_cols('name')
+    td.add_index('syndrome_types_name_key', ['lower(name)'], unique=True)
+
+    td = db.new_table('syndrome_demog_fields')
+    td.column('synddf_id', SerialColumn, primary_key = True)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('name', StringColumn)
+    td.column('label', StringColumn)
+    td.column('show_case', BooleanColumn, default = 'True')
+    td.column('show_form', BooleanColumn, default = 'True')
+    td.column('show_search', BooleanColumn, default = 'True')
+    td.column('show_person', BooleanColumn, default = 'True')
+    td.column('show_result', BooleanColumn, default = 'True')
+    td.add_index('sdf_syndrome_id', ['syndrome_id'])
+    td.add_index('sdf_si_name_idx', ['syndrome_id', 'name'], unique=True)
+
+    td = db.new_table('syndrome_case_status')
+    td.column('syndcs_id', SerialColumn, primary_key = True)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('name', StringColumn)
+    td.column('label', StringColumn)
+    td.add_index('scs_syndrome_id', ['syndrome_id'])
+
+    td = db.new_table('syndrome_case_assignments')
+    td.column('syndca_id', SerialColumn, primary_key = True)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('name', StringColumn)
+    td.column('label', StringColumn)
+    td.add_index('sca_syndrome_id', ['syndrome_id'])
+
+    td = db.new_table('form_defs')
+    td.column('name', StringColumn)
+    td.column('version', IntColumn)
+    td.column('xmldef', StringColumn)
+    td.add_index('fd_namever_idx', ['name', 'version'], unique=True)
+    td.add_index('fd_name_idx', ['name'])
+
+    td = db.new_table('forms', row_class = Form)
+    td.column('label', StringColumn, size = 20, primary_key = True)
+    td.column('form_type', StringColumn, size = 20)
+    td.column('cur_version', IntColumn)
+    td.column('name', StringColumn)
+    td.column('allow_multiple', BooleanColumn, default = 'True')
+    td.column('def_update_time', DatetimeColumn)
+    td.order_by_cols('name')
+
+    td = db.new_table('syndrome_forms')
+    td.column('syndrome_forms_id', SerialColumn, primary_key = True)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('form_label', ReferenceColumn, 
+              references = 'forms', on_update = 'cascade')
+    td.order_by_cols('syndrome_forms_id')
+    td.add_index('sf_syndrome_id', ['syndrome_id'])
+
+# Later
+#    td = db.new_table('addresses', row_class = Address)
+#    td.column('address_id', SerialColumn, primary_key = True)
+#    td.column('street_address', StringColumn)
+#    td.column('locality', StringColumn)
+#    td.column('state', StringColumn)
+#    td.column('postcode', StringColumn)
+
+    td = db.new_table('persons')
+    td.column('person_id', SerialColumn, primary_key = True)
+    td.column('last_update', LastUpdateColumn)
+    td.column('data_src', StringColumn)
+    td.column('surname', StringColumn)
+    td.column('given_names', StringColumn)
+    td.column('DOB', DateColumn)
+    td.column('DOB_prec', IntColumn, default=0)
+    td.column('sex', StringColumn, size = 1)
+    td.column('interpreter_req', StringColumn)
+    td.column('home_phone', StringColumn)
+    td.column('work_phone', StringColumn)
+    td.column('mobile_phone', StringColumn)
+    td.column('fax_phone', StringColumn)
+    td.column('e_mail', StringColumn)
+    td.column('street_address', StringColumn)
+    td.column('locality', StringColumn)
+    td.column('state', StringColumn)
+    td.column('postcode', StringColumn)
+    td.column('country', StringColumn)
+    td.column('alt_street_address', StringColumn)
+    td.column('alt_locality', StringColumn)
+    td.column('alt_state', StringColumn)
+    td.column('alt_postcode', StringColumn)
+    td.column('alt_country', StringColumn)
+    td.column('work_street_address', StringColumn)
+    td.column('work_locality', StringColumn)
+    td.column('work_state', StringColumn)
+    td.column('work_postcode', StringColumn)
+    td.column('work_country', StringColumn)
+    td.column('occupation', StringColumn)
+    td.column('passport_number', StringColumn)
+    td.column('passport_country', StringColumn)
+    td.column('passport_number_2', StringColumn)
+    td.column('passport_country_2', StringColumn)
+    td.column('indigenous_status', StringColumn)
+    td.order_by_cols('surname', 'given_names')
+    td.add_index('p_data_src_idx', ['data_src'])
+    td.add_index('p_surname_idx', ['surname'])
+    td.add_index('p_given_names_idx', ['given_names'])
+    td.add_index('p_interpreter_req_idx', ['interpreter_req'])
+    td.add_index('p_DOB_idx', ['DOB'])
+    td.add_index('p_sex_idx', ['sex'])
+    td.add_index('p_home_phone_idx', ['home_phone'])
+    td.add_index('p_work_phone_idx', ['work_phone'])
+    td.add_index('p_mobile_phone_idx', ['mobile_phone'])
+    td.add_index('p_fax_phone_idx', ['fax_phone'])
+    td.add_index('p_e_mail_idx', ['e_mail'])
+    td.add_index('p_locality_idx', ['locality'])
+    td.add_index('p_state_idx', ['state'])
+    td.add_index('p_postcode_idx', ['postcode'])
+    td.add_index('p_alt_locality_idx', ['alt_locality'])
+    td.add_index('p_alt_state_idx', ['alt_state'])
+    td.add_index('p_alt_postcode_idx', ['alt_postcode'])
+    td.add_index('p_work_locality_idx', ['work_locality'])
+    td.add_index('p_work_state_idx', ['work_state'])
+    td.add_index('p_work_postcode_idx', ['work_postcode'])
+    td.add_index('p_occupation_idx', ['occupation'])
+    td.add_index('p_passport_number_idx', ['passport_number'])
+    td.add_index('p_passport_country_idx', ['passport_country'])
+    td.add_index('p_passport_number_2_idx', ['passport_number_2'])
+    td.add_index('p_passport_country_2_idx', ['passport_country_2'])
+    td.add_index('p_indigenous_status_idx', ['indigenous_status'])
+
+    td = db.new_table('groups')
+    td.column('group_id', SerialColumn, primary_key = True)
+    td.column('group_name', StringColumn, size = 60, unique = True)
+    td.column('rights', StringColumn)
+    td.column('description', StringColumn)
+    td.order_by_cols('group_name')
+
+    td = db.new_table('unit_groups')
+    td.column('unit_groups_id', SerialColumn, primary_key = True)
+    td.column('unit_id', ReferenceColumn, 
+              references = 'units', on_update='cascade', on_delete='cascade')
+    td.column('group_id', ReferenceColumn, references = 'groups')
+    td.add_index('ug_unit_idx', ['unit_id'])
+    td.add_index('ug_group_idx', ['group_id'])
+
+    td = db.new_table('units')
+    td.column('unit_id', SerialColumn, primary_key = True)
+    td.column('enabled', BooleanColumn, default = 'False')
+    td.column('name', StringColumn, unique = True)
+    td.column('rights', StringColumn)
+    td.column('street_address', StringColumn)
+    td.column('postal_address', StringColumn)
+    td.column('contact_user_id', ReferenceColumn, 
+              references = 'users', on_update='cascade', on_delete='set null')
+    td.order_by_cols('name')
+
+    td = db.new_table('group_syndromes')
+    td.column('group_syndromes_id', SerialColumn, primary_key = True)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('group_id', ReferenceColumn, references = 'groups')
+    td.add_index('gs_unit_idx', ['syndrome_id'])
+    td.add_index('gs_group_idx', ['group_id'])
+
+    td = db.new_table('users')
+    td.column('user_id', SerialColumn, primary_key = True)
+    td.column('last_update', LastUpdateColumn)
+    td.column('enabled', BooleanColumn, default='false')
+    td.column('deleted', BooleanColumn, default='false')
+    td.column('rights', StringColumn)
+    td.column('username', StringColumn, size = 60, unique = True)
+    td.column('fullname', StringColumn, size = 60)
+    td.column('title', StringColumn)
+    td.column('agency', StringColumn)
+    td.column('expertise', StringColumn)
+    td.column('password', PasswdColumn)
+    td.column('creation_timestamp', DatetimeColumn, 
+              default = 'CURRENT_TIMESTAMP')
+    td.column('sponsoring_user_id', ReferenceColumn,
+                references='users', on_delete='set null')
+    td.column('enable_key', StringColumn)
+    td.column('checked_timestamp', DatetimeColumn) 
+    td.column('email', StringColumn)
+    td.column('phone_home', StringColumn)
+    td.column('phone_work', StringColumn)
+    td.column('phone_mobile', StringColumn)
+    td.column('phone_fax', StringColumn)
+    td.column('bad_attempts', IntColumn, default = '0')
+    td.column('bad_timestamp', DatetimeColumn)
+    td.column('preferences', BinaryColumn)
+    td.column('privacy', StringColumn)
+    td.order_by_cols('username')
+    td.add_index('u_username_idx', ['username'])
+    td.add_index('u_email_idx', ['email'])
+    td.add_index('u_enable_key_idx', ['enable_key'])
+
+    td = db.new_table('user_log')
+    td.column('user_log_id', SerialColumn, primary_key = True)
+    td.column('user_id', ReferenceColumn, 
+              references ='users', on_update='cascade', on_delete='cascade')
+    td.column('event_timestamp', DatetimeColumn, 
+              default = 'CURRENT_TIMESTAMP')
+    td.column('event_type', StringColumn)
+    td.column('remote_addr', StringColumn)
+    td.column('forwarded_addr', StringColumn)
+    td.column('case_id', ReferenceColumn, 
+              references ='cases', on_update='cascade', on_delete='cascade')
+    td.add_index('ul_user_id_idx', ['user_id'])
+    td.add_index('ul_case_id_idx', ['case_id'])
+
+    td = db.new_table('admin_log')
+    td.column('admin_log_id', SerialColumn, primary_key = True)
+    td.column('user_id', ReferenceColumn, 
+              references ='users', on_update='cascade', on_delete='cascade')
+    td.column('event_timestamp', DatetimeColumn, 
+              default = 'CURRENT_TIMESTAMP')
+    td.column('event_type', StringColumn)
+    td.column('remote_addr', StringColumn)
+    td.column('forwarded_addr', StringColumn)
+    td.add_index('al_user_id_idx', ['user_id'])
+
+    td = db.new_table('unit_users')
+    td.column('unit_user_id', SerialColumn, primary_key = True)
+    td.column('unit_id', ReferenceColumn, 
+              references = 'units', on_update='cascade', on_delete='cascade')
+    td.column('user_id', ReferenceColumn, 
+              references = 'users', on_update='cascade', on_delete='cascade')
+    td.add_index('uu_unit_idx', ['unit_id'])
+    td.add_index('uu_user_idx', ['user_id'])
+
+    td = db.new_table('tags')
+    td.column('tag_id', SerialColumn, primary_key = True)
+    td.column('tag', StringColumn)
+    td.column('notes', StringColumn)
+    td.add_index('t_tag_idx', ['tag'], unique=True)
+
+    td = db.new_table('case_tags')
+    td.column('case_tag_id', SerialColumn, primary_key = True)
+    td.column('case_id', ReferenceColumn, 
+              references ='cases(case_id)', 
+              on_update='cascade', on_delete='cascade')
+    td.column('tag_id', ReferenceColumn, 
+              references ='tags(tag_id)', 
+              on_update='cascade', on_delete='cascade')
+    td.add_index('ct_case_id_idx', ['case_id'])
+    td.add_index('ct_tag_idx', ['tag_id'])
+    td.add_index('ct_case_tag_idx', ['case_id','tag_id'], unique=True)
+
+    td = db.new_table('contact_types')
+    td.column('contact_type_id', SerialColumn, primary_key = True)
+    td.column('contact_type', StringColumn)
+    td.add_index('ct_contact_type_idx', ['lower(contact_type)'], unique=True)
+
+    td = db.new_table('case_contacts')
+    td.column('case_id', ReferenceColumn, primary_key=True,
+              references ='cases(case_id)', 
+              on_update='cascade', on_delete='cascade')
+    td.column('contact_id', ReferenceColumn, primary_key=True,
+              references ='cases(case_id)', 
+              on_update='cascade', on_delete='cascade')
+    td.column('contact_type_id', ReferenceColumn, 
+              references='contact_types', on_delete='SET NULL')
+    td.column('contact_date', DatetimeColumn)
+    td.add_index('cc_case_id_idx', ['case_id'])
+    td.add_index('cc_contact_id_idx', ['contact_id'])
+    td.add_index('cc_contact_type_id_idx', ['contact_type_id'])
+
+    td = db.new_table('cases')
+    td.column('case_id', SerialColumn, primary_key = True)
+    td.column('last_update', LastUpdateColumn)
+    td.column('deleted', BooleanColumn, default='False')
+    td.column('delete_reason', StringColumn)
+    td.column('delete_timestamp', DatetimeColumn)
+    td.column('local_case_id', StringColumn)
+    td.column('person_id', ReferenceColumn, references = 'persons')
+    td.column('syndrome_id', ReferenceColumn, references = 'syndrome_types')
+    td.column('case_status', StringColumn)
+    td.column('case_assignment', StringColumn)
+    td.column('notification_datetime', DatetimeColumn)
+    td.column('onset_datetime', DatetimeColumn)
+    td.column('notifier_name', StringColumn)
+    td.column('notifier_contact', StringColumn)
+    td.column('notes', StringColumn)
+    td.order_by_cols('onset_datetime')
+    td.add_index('c_person_idx', ['person_id'])
+    td.add_index('c_deleted_idx', ['deleted'])
+    td.add_index('c_case_status_idx', ['case_status'])
+    td.add_index('c_case_assignment_idx', ['case_assignment'])
+    td.add_index('c_syndrome_idx', ['syndrome_id'])
+    td.add_index('c_local_idx', ['local_case_id'])
+
+    td = db.new_table('case_acl')
+    td.column('case_acl_id', SerialColumn, primary_key = True)
+    td.column('unit_id', ReferenceColumn, references = 'units')
+    td.column('case_id', ReferenceColumn, 
+              references = 'cases', on_delete = 'cascade')
+    td.add_index('cacl_unit_idx', ['unit_id'])
+    td.add_index('cacl_case_idx', ['case_id'])
+
+    td = db.new_table('case_form_summary')
+    td.column('summary_id', SerialColumn, primary_key = True)
+    td.column('case_id', ReferenceColumn, references = 'cases', 
+              on_delete='cascade')
+    td.column('form_label', ReferenceColumn, references = 'forms',
+              on_update='cascade')
+    td.column('form_version', IntColumn)
+    td.column('form_date', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.column('data_src', StringColumn)
+    td.column('deleted', BooleanColumn, default='False')
+    td.column('delete_reason', StringColumn)
+    td.column('delete_timestamp', DatetimeColumn)
+    td.column('summary', StringColumn)
+    td.order_by_cols('form_date')
+    td.add_index('cfs_case_idx', ['case_id'])
+
+    td = db.new_table('workqueues')
+    td.column('queue_id', SerialColumn, primary_key = True)
+    td.column('name', StringColumn)
+    td.column('description', StringColumn)
+    td.column('unit_id', ReferenceColumn, 
+        references = 'units', on_delete = 'cascade', unique=True)
+    td.column('user_id', ReferenceColumn, 
+        references = 'users', on_delete = 'cascade', unique=True)
+    td.add_index('wq_name_idx', ['name'], unique=True)
+
+    td = db.new_table('workqueue_members')
+    td.column('wqm_id', SerialColumn, primary_key = True)
+    td.column('queue_id', ReferenceColumn, 
+        references = 'workqueues', on_delete = 'cascade')
+    td.column('unit_id', ReferenceColumn, 
+        references = 'units', on_delete = 'cascade')
+    td.column('user_id', ReferenceColumn, 
+        references = 'users', on_delete = 'cascade')
+    td.add_index('wqm_queue_idx', ['queue_id'])
+    td.add_index('wqm_unit_idx', ['unit_id'])
+    td.add_index('wqm_user_idx', ['user_id'])
+    td.add_index('wqm_qunit_idx', ['queue_id', 'unit_id'], unique=True)
+    td.add_index('wqm_quser_idx', ['queue_id', 'user_id'], unique=True)
+
+    td = db.new_table('tasks')
+    td.column('task_id', SerialColumn, primary_key = True)
+    td.column('parent_task_id', ReferenceColumn, 
+                references = 'tasks', on_delete = 'cascade')
+    td.column('queue_id', ReferenceColumn, references = 'workqueues')
+    td.column('action', IntColumn)
+    td.column('active_date', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.column('due_date', DatetimeColumn)
+    td.column('completed_by_id', ReferenceColumn, 
+                references = 'users', on_delete = 'SET NULL')
+    td.column('completed_date', DatetimeColumn)
+    td.column('locked_by_id', ReferenceColumn, 
+                references = 'users', on_delete = 'SET NULL')
+    td.column('locked_date', DatetimeColumn)
+    td.column('task_description', StringColumn)
+    td.column('annotation', StringColumn)
+    td.column('case_id', ReferenceColumn, 
+                references = 'cases', on_delete = 'SET NULL')
+    td.column('form_name', ReferenceColumn, 
+                references='forms', on_delete='SET NULL', on_update='CASCADE')
+    td.column('summary_id', ReferenceColumn, 
+                references = 'case_form_summary', on_delete = 'SET NULL')
+    td.column('assigner_id', ReferenceColumn, 
+                references = 'users', on_delete = 'SET NULL')
+    td.column('assignment_date', DatetimeColumn, 
+                default = 'CURRENT_TIMESTAMP')
+    td.column('originator_id', ReferenceColumn, 
+                references = 'users', on_delete = 'SET NULL')
+    td.column('creation_date', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.add_index('tasks_queue_idx', ['queue_id'])
+    td.add_index('tasks_active_date_idx', ['active_date'])
+    td.add_index('tasks_completed_date_idx', ['completed_date'])
+    td.add_index('tasks_assigner_idx', ['assigner_id'])
+
+    td = db.new_table('bulletins')
+    td.column('bulletin_id', SerialColumn, primary_key = True)
+    td.column('post_date', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.column('expiry_date', DatetimeColumn)
+    td.column('title', StringColumn, size = 60)
+    td.column('synopsis', StringColumn)
+    td.column('detail', StringColumn)
+    td.order_by_cols('post_date')
+    td.add_index('b_post_idx', ['post_date'])
+    td.add_index('b_expiry_idx', ['expiry_date'])
+
+    td = db.new_table('group_bulletins')
+    td.column('group_bulletins_id', SerialColumn, primary_key = True)
+    td.column('bulletin_id', ReferenceColumn, 
+              references = 'bulletins', on_delete = 'cascade')
+    td.column('group_id', ReferenceColumn, 
+              references = 'groups', on_delete = 'cascade')
+    td.add_index('gb_bulletin_idx', ['bulletin_id'])
+    td.add_index('gb_group_idx', ['group_id'])
+
+    td = db.new_table('person_phonetics')
+    td.column('person_id', ReferenceColumn, 
+              references = 'persons', on_delete = 'cascade')
+    td.column('phonetics', StringColumn)
+    td.add_index('pp_person_id_idx', ['person_id'])
+    td.add_index('pp_phonetics_idx', ['phonetics'])
+
+    td = db.new_table('report_params')
+    td.column('report_params_id', SerialColumn, primary_key = True)
+    td.column('label', StringColumn)
+    td.column('type', StringColumn)
+    td.column('sharing', StringColumn)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('unit_id', ReferenceColumn, 
+        references = 'units', on_delete = 'cascade')
+    td.column('user_id', ReferenceColumn, 
+        references = 'users', on_delete = 'cascade')
+    td.column('pickle', StringColumn)
+    td.column('xmldef', StringColumn)
+    td.add_index('rp_label_idx', ['label'])
+    td.add_index('rp_type_idx', ['type'])
+    td.add_index('rp_sharing_idx', ['sharing'])
+    td.add_index('rp_unit_idx', ['unit_id'])
+    td.add_index('rp_user_idx', ['user_id'])
+
+    td = db.new_table('dupe_persons')
+    td.column('low_person_id', ReferenceColumn, 
+              references='persons', primary_key=True, on_delete='cascade')
+    td.column('high_person_id', ReferenceColumn, 
+              references='persons', primary_key=True, on_delete='cascade')
+    td.column('status', StringColumn, size=1)
+    td.column('confidence', FloatColumn)
+    td.column('exclude_reason', StringColumn)
+    td.column('timechecked', DatetimeColumn, default = 'CURRENT_TIMESTAMP')
+    td.add_index('dp_timechecked_idx', ['timechecked'])
+
+    td = db.new_table('address_states')
+    td.column('address_states_id', SerialColumn, primary_key = True)
+    td.column('code', StringColumn)
+    td.column('label', StringColumn)
+
+    td = db.new_table('import_defs')
+    td.column('import_defs_id', SerialColumn, primary_key = True)
+    td.column('name', StringColumn)
+    td.column('syndrome_id', ReferenceColumn, 
+              references = 'syndrome_types', on_delete = 'cascade')
+    td.column('xmldef', StringColumn)
+    td.add_index('id_name_idx', ['name'])
+
+    td = db.new_table('nicknames')
+    td.column('nick', StringColumn)
+    td.column('alt', StringColumn)
+    td.add_index('nick_idx', ['nick'])
+    td.add_index('nick_alt_idx', ['nick', 'alt'])
+
+    td = db.new_table('casesets')
+    td.column('caseset_id', SerialColumn, primary_key = True)
+    td.column('name', StringColumn)
+    td.column('dynamic', BooleanColumn)
+    td.column('unit_id', ReferenceColumn, 
+        references = 'units', on_delete = 'cascade')
+    td.column('user_id', ReferenceColumn, 
+        references = 'users', on_delete = 'cascade')
+    td.column('pickle', BinaryColumn)
+    td.add_index('cs_label_idx', ['name'])
+
+    return db
+
+if __name__ == '__main__':
+    import config
+    dsn = sys.argv[1]
+    define_db(dsn).make_database(config.web_user)
diff --git a/casemgr/schema/seed.py b/casemgr/schema/seed.py
new file mode 100644
index 0000000..3589c93
--- /dev/null
+++ b/casemgr/schema/seed.py
@@ -0,0 +1,145 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj
+
+def check(db, table, **key):
+    """
+    Checks to see if the row uniquely identified by the key-value pairs
+    exists, and if not, yields up a single new row with the key-values
+    pre-populated.
+    """
+    query = db.query(table)
+    for k, v in key.iteritems():
+        query.where('%s=%%s' % k, v)
+    if query.fetchone() is None:
+        row = db.new_row(table)
+        for k, v in key.iteritems():
+            setattr(row, k, v)
+        yield row
+
+
+def if_empty_insert(db, table, data, *cols):
+    """
+    If the named table has no rows, then insert the given list of row
+    tuples into the named columns.
+    """
+    if db.query(table, limit=1).fetchone() is None:
+        cmd = 'INSERT INTO %s (%s) VALUES (%s)' %\
+            (table, ','.join(cols), ','.join(['%s'] * len(cols)))
+        curs = db.cursor()
+        try:
+            for row in data:
+                dbobj.execute(curs, cmd, row)
+        finally:
+            curs.close()
+
+
+def seed_db(db):
+    """
+    Create essential entities, such as the admin user, unit and group
+    """
+    for user in check(db, 'users', username='admin'):
+        from casemgr import credentials
+        import getpass
+        user = db.new_row('users')
+        user.user_id = 0
+        user.enabled = True
+        user.rights = 'ADMIN'
+        user.username = 'admin'
+        user.fullname = 'Administrator'
+        user.role = 'Administrator'
+        while 1:
+            pwd = credentials.NewPass()
+            print 'Setting initial administrator password'
+            pwd.new_a = getpass.getpass('Administrator password: ')
+            pwd.new_b = getpass.getpass('Re-enter administrator password: ')
+            try:
+                pwd.set(user)
+            except credentials.PasswordError, e:
+                print str(e)
+            else:
+                user.db_update()
+                break
+
+    # Admin unit
+    for unit in check(db, 'units', unit_id=0):
+        unit.enabled = True
+        unit.name = 'Administrator Unit'
+        unit.db_update()
+
+    # Admin group
+    for group in check(db, 'groups', group_id=0):
+        group.group_name = 'Administrator Group'
+        group.rights = 'ACCESSALL,EXPORT'
+        group.description = 'Administrator Group'
+        group.db_update()
+
+    # unit_groups
+    for unit_group in check(db, 'unit_groups', unit_id=0, group_id=0):
+        unit_group.db_update()
+
+    # unit_users
+    for unit_user in check(db, 'unit_users', unit_id=0, user_id=0):
+        unit_user.db_update()
+
+    # address_states (this is Australia-specific, but serves as an example, and
+    # can be overridden from within the admin interface)
+    states = [
+        ('NSW', 'New South Wales'), 
+        ('ACT', 'Australian Capital Territory'), 
+        ('NT', 'Northern Territory'),
+        ('QLD', 'Queensland'), 
+        ('SA', 'South Australia'), 
+        ('TAS', 'Tasmania'), 
+        ('VIC', 'Victoria'),
+        ('WA', 'Western Australia'),
+    ]
+    if_empty_insert(db, 'address_states', states, 'code', 'label')
+    # case assignments (this is NSWDoH-specific, but serves as an example, and
+    # can be overridden from within the admin interface)
+    assignments = [
+        ('GSAHS_G', 'Greater Southern AHS (Goulburn)'),
+        ('GSAHS_A', 'Greater Southern AHS (Albury)'),
+        ('GWAHS_BH', 'Greater Western AHS (Broken Hill)'),
+        ('GWAHS_D', 'Greater Western AHS (Dubbo)'),
+        ('GWAHS_B', 'Greater Western AHS (Bathurst)'),
+        ('HNEAHS_N', 'Hunter/New England AHS (Newcastle)'),
+        ('HNEAHS_T', 'Hunter/New England AHS (Tamworth)'),
+        ('JH', 'Justice Health Service'),
+        ('NCAHS_PM', 'North Coast AHS (Port Macquarie)'),
+        ('NCAHS_L', 'North Coast AHS (Lismore)'),
+        ('NSCCAHS_H', 'Northern Sydney/Central Coast AHS (Hornsby)'),
+        ('NSCCAHS_G', 'Northern Sydney/Central Coast AHS (Gosford)'),
+        ('SESIAHS_R', 'South Eastern Sydney/Illawarra AHS (Randwick)'),
+        ('SESIAHS_W', 'South Eastern Sydney/Illawarra AHS (Wollongong)'),
+        ('SSWAHS_C', 'Sydney South West AHS (Camperdown)'),
+        ('SWAHS_PA', 'Sydney West AHS (Parramatta)'),
+        ('SWAHS_PE', 'Sydney West AHS (Penrith)'),
+        ('OTHER', 'Other'),
+    ]
+    if_empty_insert(db, 'syndrome_case_assignments', assignments, 'name', 'label')
+    #
+    from casemgr.nicknames import nicknames
+    if_empty_insert(db, 'nicknames', nicknames, 'nick', 'alt')
+    # contact_types
+    contact_types = [
+        ('Household',),
+        ('Workplace/School',),
+    ]
+    if_empty_insert(db, 'contact_types', contact_types, 'contact_type')
diff --git a/casemgr/schema/upgrade.py b/casemgr/schema/upgrade.py
new file mode 100644
index 0000000..8bfa664
--- /dev/null
+++ b/casemgr/schema/upgrade.py
@@ -0,0 +1,865 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Perform any necessary schema changes.
+"""
+
+import os, sys
+import textwrap
+from cocklebur import dbobj, form_ui
+
+class UPMeta(type):
+
+    def __init__(cls, name, bases, dict):
+        if 'test' in dict:
+            cls.upgrades.append(cls)
+
+
+class Upgrades(object):
+
+    """
+    Upgrades maintains a registry of all subclasses (thanks to UPMeta), runs
+    their .test() methods in reverse order, and then runs the appropriate
+    .upgrade() methods.
+    """
+
+    __metaclass__ = UPMeta
+    upgrades = []
+
+    def __init__(self, db, target_dir):
+        self.db = db
+        self.target_dir = target_dir
+
+    def msg(self, text):
+        text = ' '.join([line.strip() for line in text.splitlines()])
+        print textwrap.fill(text, initial_indent='  schema upgrade: ', 
+                            subsequent_indent='    ')
+
+    def abort(self, text):
+        text = ' '.join([line.strip() for line in text.splitlines()])
+        text = textwrap.wrap(text, initial_indent='Schema upgrade ABORTED: ', 
+                             subsequent_indent='    ')
+        tear = '!' * 80
+        sys.exit('\n'.join([tear] + text + [tear]))
+
+    def run(self):
+        try:
+            self.db.cursor().close()
+        except dbobj.DatabaseError:
+            return            # Probably new DB
+        needed = []
+        for upg_cls in self.upgrades[::-1]:
+            upg = upg_cls(self.db, self.target_dir)
+            if upg.test():
+                break
+            needed.append(upg)
+        for upg in needed[::-1]:
+            self.msg(upg.__doc__)
+            try:
+                upg.upgrade()
+            except dbobj.DatabaseError, e:
+                sys.exit(e)
+
+    def has_relation(self, name):
+        return self.db.db_has_relation(name)
+
+    def has_col(self, tablecol):
+        table, col = tablecol.split('.')
+        if not self.has_relation(table):
+            return False
+        curs = self.db.cursor()
+        try:
+            try:
+                dbobj.execute(curs, 'SELECT %s FROM %s LIMIT 0' % (col, table))
+            except dbobj.DatabaseError:
+                self.db.rollback()
+                return False
+            else:
+                return True
+        finally:
+            curs.close()
+
+    def update_constraint(self, ltable, ftable, updtype, deltype, *sql):
+        # types: 
+        #    a - no action (default)
+        #    c - cascade
+        #    n - null
+        if self.has_relation(ltable) and self.has_relation(ftable):
+            curs = self.db.cursor()
+            try:
+                dbobj.execute(curs, "SELECT conname"
+                                "  FROM pg_constraint"
+                                "  JOIN pg_class AS l ON (l.oid = conrelid)"
+                                "  JOIN pg_class AS f ON (f.oid = confrelid)"
+                                "  WHERE l.relname = %s"
+                                "    AND f.relname = %s"
+                                "    AND confupdtype = %s"
+                                "    AND confdeltype = %s", 
+                                (ltable, ftable, updtype, deltype))
+                rows = curs.fetchall()
+                if len(rows) == 1:
+                    rel = rows[0][0]
+                    # print 'schema upgrade: constraint %r on %r' % (rel, ltable)
+                    dbobj.execute(curs, 'ALTER TABLE %s DROP CONSTRAINT "%s"' % 
+                                            (ltable, rel))
+                    for cmd in sql:
+                        dbobj.execute(curs, cmd)
+            finally:
+                curs.close()
+
+    def agg(self, cmd, *args):
+        curs = self.db.cursor()
+        try:
+            dbobj.execute(curs, cmd, args)
+            row = curs.fetchone()
+            assert row is not None and len(row) == 1
+            return row[0]
+        finally:
+            curs.close()
+
+    def __call__(self, cmd):
+        curs = self.db.cursor()
+        try:
+            dbobj.execute(curs, cmd)
+        finally:
+            curs.close()
+
+
+class _UG(Upgrades):
+    """
+    add case_id on user_log [1367] (20050428-1)
+    """
+
+    def test(self):
+        return self.has_col('user_log.case_id')
+
+    def upgrade(c):
+        c('ALTER TABLE user_log ADD COLUMN case_id INTEGER'
+            ' REFERENCES cases(case_id) ON DELETE CASCADE')
+        c('CREATE INDEX ul_case_id_idx ON user_log (case_id)')
+
+
+class _UG(Upgrades):
+    """
+    users.rights column added [1533] 20060208-1
+    """
+
+    def test(self):
+        return self.has_col('users.rights')
+
+    def upgrade(c):
+        c('CREATE TABLE users_old (LIKE users)')
+        c('INSERT INTO users_old SELECT * FROM users')
+        c('ALTER TABLE users ADD COLUMN rights TEXT')
+        c("UPDATE users SET rights='ADMIN' WHERE admin=true")
+        c('ALTER TABLE users DROP COLUMN admin')
+
+
+class _UG(Upgrades):
+    """
+    users.preferences column added [1545] 20060209-1
+    """
+
+    def test(self):
+        return self.has_col('users.preferences')
+
+    def upgrade(c):
+        c('ALTER TABLE users ADD COLUMN preferences BYTEA')
+        c("UPDATE groups SET rights='ACCESSALL' WHERE rights='DOH'")
+
+
+class _UG(Upgrades):
+    """
+    XML forms [1581]
+    """
+
+    def test(self):
+        return self.has_relation('form_defs')
+
+    def upgrade(c):
+        c('CREATE TABLE form_defs ('
+          '    name TEXT,'
+          '    version INTEGER,'
+          '    xmldef TEXT'
+          ') WITH OIDS')
+        form_dir = os.path.join(self.target_dir, 'forms')
+        if not os.path.exists(form_dir):
+            return
+        srclib = form_ui.FormLibPyFiles(form_dir)
+        dstlib = form_ui.FormLibXMLDB(self.db, 'form_defs')
+        if len(srclib) > 0 and len(dstlib) == 0:
+            for entry in srclib:
+                try:
+                    form = entry.load()
+                except form_ui.FormError, e:
+                    raise 'ERROR loading %r: %s' % (entry, e)
+                else:
+                    dstlib.save(form, entry.name, entry.version)
+
+
+class _UG(Upgrades):
+    """
+    def_update_time column added to forms [1631] 20060404-1
+    """
+
+    def test(self):
+        return self.has_col('forms.def_update_time')
+
+    def upgrade(c):
+        c('ALTER TABLE forms ADD COLUMN def_update_time timestamp')
+
+
+class _UG(Upgrades):
+    """
+    DOB_is_approx, interpreter_req added to persons [1671] 20060501-1
+    """
+
+    def test(self):
+        return self.has_col('persons.DOB_is_approx')
+
+    def upgrade(c):
+        c('ALTER TABLE persons ADD COLUMN DOB_is_approx BOOLEAN')
+        c('ALTER TABLE persons ALTER COLUMN DOB_is_approx SET DEFAULT FALSE')
+        c('ALTER TABLE persons ADD COLUMN interpreter_req VARCHAR')
+
+
+class _UG(Upgrades):
+    """
+    Configurable demographic fields [1666]
+    """
+
+    def test(self):
+        return self.has_relation('syndrome_demog_fields')
+
+    def upgrade(c):
+        c('CREATE TABLE syndrome_demog_fields ('
+          '    synddf_id SERIAL PRIMARY KEY,'
+          '    syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id)'
+          '             ON DELETE cascade,'
+          '    name TEXT,'
+          '    label TEXT,'
+          '    show_case BOOLEAN DEFAULT True,'
+          '    show_form BOOLEAN DEFAULT True,'
+          '    show_search BOOLEAN DEFAULT True,'
+          '    show_person BOOLEAN DEFAULT True'
+          ') WITH OIDS')
+
+
+class _UG(Upgrades):
+    """
+    Per-syndrome case status [1676]
+    """
+
+    def test(self):
+        return self.has_relation('syndrome_case_status')
+
+    def upgrade(c):
+        c('CREATE TABLE syndrome_case_status ('
+          '    syndcs_id SERIAL PRIMARY KEY,'
+          '    syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id)'
+          '             ON DELETE cascade,'
+          '    name TEXT,'
+          '    label TEXT'
+          ') WITH OIDS')
+
+
+class _UG(Upgrades):
+    """
+    person mobile, fax, e-mail and passport [1726] added 2006-05-10
+    """
+
+    def test(self):
+        return self.has_col('persons.mobile_phone')
+
+    def upgrade(c):
+        c('ALTER TABLE persons ADD COLUMN mobile_phone VARCHAR')
+        c('ALTER TABLE persons ADD COLUMN fax_phone VARCHAR')
+        c('ALTER TABLE persons ADD COLUMN e_mail VARCHAR')
+        c('ALTER TABLE persons ADD COLUMN passport_number VARCHAR')
+        c('ALTER TABLE persons ADD COLUMN passport_country VARCHAR')
+
+
+class _UG(Upgrades):
+    """
+    Unified cases & contacts, branch merge [1761] [1831] 2006-05-19
+    """
+
+    def test(self):
+        return self.has_col('cases.master_id')
+
+    def upgrade(c):
+        c('CREATE TABLE contact_syndromes ('
+          '    cs_id SERIAL PRIMARY KEY,'
+          '    syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id)'
+          '             ON DELETE cascade,'
+          '    contact_syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id)'
+          '             ON DELETE cascade'
+          ') WITH OIDS')
+        c('ALTER TABLE cases ADD COLUMN master_id INT')
+        c('ALTER TABLE cases ADD FOREIGN KEY (master_id)'
+                    ' REFERENCES cases(case_id)')
+        c('ALTER TABLE cases ADD COLUMN master_syndrome_id int')
+        c('ALTER TABLE cases ADD FOREIGN KEY (master_syndrome_id)'
+                    ' REFERENCES syndrome_types(syndrome_id)')
+        c('UPDATE cases SET master_id=case_id WHERE master_id IS null')
+        c('UPDATE cases SET master_syndrome_id=syndrome_id')
+        c('UPDATE cases SET master_id=case_contacts.case_id'
+          ' WHERE cases.case_id=case_contacts.contact_id')
+        c('UPDATE cases SET master_syndrome_id=tmp.syndrome_id'
+          ' FROM cases AS tmp'
+          ' WHERE cases.master_id=tmp.case_id')
+        c('DROP TABLE case_contacts')
+        c('ALTER TABLE case_acl RENAME case_id TO master_id')
+
+
+class _UG(Upgrades):
+    """
+    add show_result to syndrome_demog_fields after [1863]
+    """
+
+    def test(self):
+        return self.has_col('syndrome_demog_fields.show_result')
+
+    def upgrade(c):
+        c('ALTER TABLE syndrome_demog_fields ADD COLUMN show_result BOOLEAN')
+        c('ALTER TABLE syndrome_demog_fields'
+          ' ALTER COLUMN show_result SET DEFAULT TRUE')
+
+
+class _UG(Upgrades):
+    """
+    logical delete cases after [1879] [1880]
+    """
+
+    def test(self):
+        return self.has_col('cases.deleted')
+
+    def upgrade(c):
+        c('ALTER TABLE cases ADD COLUMN deleted BOOLEAN')
+        c('ALTER TABLE cases ALTER COLUMN deleted SET DEFAULT FALSE')
+        c("UPDATE cases SET deleted=TRUE WHERE case_status='excluded'")
+        c('UPDATE cases SET deleted=FALSE WHERE deleted IS NULL')
+        c('CREATE INDEX c_deleted_idx ON cases (deleted)')
+
+
+class _UG(Upgrades):
+    """
+    workflow-lite [2042]
+    """
+
+    def test(self):
+        return self.has_relation('tasks')
+
+    def upgrade(c):
+        c('CREATE TABLE workqueues ('
+          '    queue_id SERIAL PRIMARY KEY,'
+          '    name TEXT,'
+          '    description TEXT,'
+          '    unit_id INTEGER REFERENCES units(unit_id) ON DELETE cascade UNIQUE,'
+          '    user_id INTEGER REFERENCES users(user_id) ON DELETE cascade UNIQUE'
+          ') WITH OIDS')
+        c('CREATE TABLE workqueue_members ('
+          '    wqm_id SERIAL PRIMARY KEY,'
+          '    queue_id INTEGER REFERENCES workqueues(queue_id) ON DELETE cascade,'
+          '    unit_id INTEGER REFERENCES units(unit_id) ON DELETE cascade,'
+          '    user_id INTEGER REFERENCES users(user_id) ON DELETE cascade'
+          ') WITH OIDS')
+        c('CREATE TABLE tasks ('
+          '    task_id SERIAL PRIMARY KEY,'
+          '    parent_task_id INTEGER REFERENCES tasks(task_id) ON DELETE cascade,'
+          '    queue_id INTEGER REFERENCES workqueues(queue_id),'
+          '    action INTEGER,'
+          '    active_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,'
+          '    due_date TIMESTAMP,'
+          '    completed_by_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,'
+          '    completed_date TIMESTAMP,'
+          '    locked_by_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,'
+          '    locked_date TIMESTAMP,'
+          '    task_description TEXT,'
+          '    annotation TEXT,'
+          '    master_id INTEGER REFERENCES cases(case_id) ON DELETE SET NULL,'
+          '    case_id INTEGER REFERENCES cases(case_id) ON DELETE SET NULL,'
+          '    form_name VARCHAR(20) REFERENCES forms(label) ON DELETE SET NULL,'
+          '    summary_id INTEGER REFERENCES case_form_summary(summary_id) ON DELETE SET NULL,'
+          '    assigner_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,'
+          '    assignment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,'
+          '    originator_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,'
+          '    creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP'
+          ') WITH OIDS')
+
+
+class _UG(Upgrades):
+    """
+    0.95: fix form_name constrain [2069], indigenous_status to persons
+    [2071], additional_info to syndrome_types [2083]
+    """
+
+    def test(self):
+        return self.has_col('persons.indigenous_status')
+
+    def upgrade(c):
+        c.update_constraint('tasks', 'forms', 'a', 'n',
+                          'ALTER TABLE tasks ADD'
+                          '  FOREIGN KEY (form_name) REFERENCES forms(label)'
+                          '  ON DELETE SET NULL ON UPDATE CASCADE')
+        c('ALTER TABLE persons ADD COLUMN indigenous_status TEXT')
+        c('CREATE INDEX p_indigenous_status_idx ON persons (indigenous_status)')
+        c('syndrome_types', 'additional_info',
+          'ALTER TABLE syndrome_types ADD COLUMN additional_info TEXT')
+
+
+class _UG(Upgrades):
+    """
+    0.96: master_id to user_log [2238], alt addr to persons [2293],
+    fix case_form_summary constraint [2301]
+    """
+
+    def test(self):
+        return self.has_col('user_log.master_id')
+
+    def upgrade(c):
+        c('ALTER TABLE user_log ADD COLUMN master_id INT REFERENCES cases')
+        c('UPDATE user_log SET master_id = case_id')
+        c('ALTER TABLE persons ADD COLUMN alt_street_address TEXT')
+        c('ALTER TABLE persons ADD COLUMN alt_locality TEXT')
+        c('ALTER TABLE persons ADD COLUMN alt_state TEXT')
+        c('ALTER TABLE persons ADD COLUMN alt_postcode TEXT')
+        c.update_constraint('case_form_summary', 'cases', 'a', 'a',
+                          'ALTER TABLE case_form_summary ADD'
+                          '  FOREIGN KEY (case_id) REFERENCES cases(case_id)'
+                          '  ON DELETE CASCADE')
+
+
+class _UG(Upgrades):
+    """
+    Report-lite [2395]
+    """
+
+    def test(self):
+        return self.has_relation('report_params')
+
+    def upgrade(c):
+        c('CREATE TABLE report_params ('
+          ' report_params_id SERIAL PRIMARY KEY,'
+          ' label TEXT,'
+          ' syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id)'
+          '     ON DELETE cascade,'
+          ' unit_id INTEGER REFERENCES units(unit_id) ON DELETE cascade,'
+          ' user_id INTEGER REFERENCES users(user_id) ON DELETE cascade,'
+          ' pickle TEXT'
+          ') WITH OIDS')
+        c('CREATE INDEX rp_label_idx ON report_params (label)')
+
+
+class _UG(Upgrades):
+    """
+    duplicate person scan exclusions [2499]
+    """
+
+    def test(self):
+        return self.has_relation('nondupe_persons')
+
+    def upgrade(c):
+        c('CREATE TABLE nondupe_persons ('
+          ' low_person_id INTEGER REFERENCES persons(person_id)'
+          '     ON DELETE cascade,'
+          ' high_person_id INTEGER REFERENCES persons(person_id)'
+          '     ON DELETE cascade,'
+          ' exclude_reason TEXT'
+          ') WITH OIDS')
+        c('CREATE UNIQUE INDEX ndp_uniq'
+          '     ON nondupe_persons (low_person_id,high_person_id)')
+
+
+class _UG(Upgrades):
+    """
+    localisation of address states [2769]
+    """
+
+    def test(self):
+        return self.has_relation('address_states')
+
+    def upgrade(c):
+        c('CREATE TABLE address_states ('
+          '     address_states_id SERIAL PRIMARY KEY, '
+          '     syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id) '
+          '         ON DELETE cascade,'
+          '     code TEXT,'
+          '     label TEXT'
+          ') WITH OIDS')
+
+
+class _UG(Upgrades):
+    """
+    1.0.4: logical user delete [2908]
+    """
+
+    def test(self):
+        return self.has_col('users.deleted')
+
+    def upgrade(c):
+        c('ALTER TABLE users ADD COLUMN deleted BOOLEAN')
+        c('UPDATE users SET deleted=false')
+        c('ALTER TABLE users ALTER deleted SET DEFAULT false')
+
+
+class _UG(Upgrades):
+    """
+    1.0.5: explicit syndrome ordering [3012]
+    """
+
+    def test(self):
+        return self.has_col('syndrome_types.priority')
+
+    def upgrade(c):
+        c('ALTER TABLE syndrome_types ADD COLUMN priority INTEGER')
+        c('UPDATE syndrome_types SET priority=3')
+
+
+class _UG(Upgrades):
+    """
+    data import (import_defs table) [3120]
+    """
+
+    def test(self):
+        return self.has_relation('import_defs')
+
+    def upgrade(c):
+        c('CREATE TABLE import_defs ('
+          ' import_defs_id SERIAL PRIMARY KEY,'
+          ' name TEXT,'
+          ' syndrome_id INTEGER REFERENCES syndrome_types(syndrome_id) '
+          '     ON DELETE cascade,'
+          ' xmldef TEXT'
+          ') WITH OIDS')
+        c('CREATE INDEX id_name_idx ON import_defs (name)')
+
+
+class _UG(Upgrades):
+    """
+    1.1.0: Additional person fields (work address, second passport,
+    occupation, etc) [3126] [3130] [3142] [3143] [3149] [3281]
+    """
+
+    def test(self):
+        return self.has_col('persons.work_street_address')
+
+    def upgrade(c):
+        # work address [3126]
+        c('ALTER TABLE persons ADD COLUMN work_street_address TEXT')
+        c('ALTER TABLE persons ADD COLUMN work_locality TEXT')
+        c('ALTER TABLE persons ADD COLUMN work_state TEXT')
+        c('ALTER TABLE persons ADD COLUMN work_postcode TEXT')
+        # second passport, occupation [3130]
+        c('ALTER TABLE persons ADD COLUMN passport_number_2 TEXT')
+        c('ALTER TABLE persons ADD COLUMN passport_country_2 TEXT')
+        c('ALTER TABLE persons ADD COLUMN occupation TEXT')
+        # address country [3142]
+        c('ALTER TABLE persons ADD COLUMN country TEXT')
+        c('ALTER TABLE persons ADD COLUMN alt_country TEXT')
+        c('ALTER TABLE persons ADD COLUMN work_country TEXT')
+        # case notes [3143]
+        c('ALTER TABLE cases ADD COLUMN notes TEXT')
+        # notifier name and contact details [3149]
+        c('ALTER TABLE cases ADD COLUMN notifier_name TEXT')
+        c('ALTER TABLE cases ADD COLUMN notifier_contact TEXT')
+        # data_src [3281]
+        c('ALTER TABLE persons ADD COLUMN data_src TEXT')
+
+
+class _UG(Upgrades):
+    """
+    nickname remapping table [3180]
+    """
+
+    def test(self):
+        return self.has_relation('nicknames')
+
+    def upgrade(c):
+        c('CREATE TABLE nicknames ('
+          '  nick TEXT,'
+          '  alt TEXT'
+          ') WITH OIDS')
+        c('CREATE INDEX nick_idx ON nicknames (nick)')
+        c('CREATE INDEX nick_alt_idx ON nicknames (nick,alt)')
+
+
+class _UG(Upgrades):
+    """
+    dupe_persons replaced nondupe_persons after 1.2 [3238]
+    """
+
+    def test(self):
+        return self.has_relation('dupe_persons')
+
+    def upgrade(c):
+        c('''
+            CREATE TABLE dupe_persons (
+                low_person_id INTEGER REFERENCES persons(person_id) 
+                    ON DELETE cascade,
+                high_person_id INTEGER REFERENCES persons(person_id) 
+                    ON DELETE cascade,
+                status VARCHAR(1),
+                confidence DOUBLE PRECISION,
+                exclude_reason TEXT,
+                timechecked TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+                PRIMARY KEY (low_person_id, high_person_id)
+            ) WITH OIDS''')
+        c('INSERT INTO dupe_persons (low_person_id, high_person_id, '
+          '                          status, exclude_reason)'
+          ' SELECT low_person_id, high_person_id, \'E\', exclude_reason'
+          '  FROM nondupe_persons')
+        c('DROP TABLE nondupe_persons')
+
+
+class _UG(Upgrades):
+    """
+    1.3.0: unit/group retasked into roles and contexts [3530] 
+    [3535] [3548], formimport [3637]
+    """
+
+    def test(self):
+        return self.has_col('users.title')
+
+    def upgrade(c):
+        c('ALTER TABLE users RENAME role TO title')
+        c('ALTER TABLE units ADD COLUMN rights VARCHAR')
+        c('ALTER TABLE groups ALTER rights TYPE VARCHAR')
+        c('ALTER TABLE users ADD COLUMN privacy VARCHAR')
+        c('ALTER TABLE users ADD COLUMN checked_timestamp TIMESTAMP')
+        c('ALTER TABLE users ADD COLUMN agency VARCHAR')
+        c('ALTER TABLE users ADD COLUMN expertise VARCHAR')
+        c('ALTER TABLE case_form_summary ADD COLUMN data_src VARCHAR')
+
+
+class _UG(Upgrades):
+    """
+    1.4.0: report types [3772] [3649]
+    """
+
+    def test(self):
+        return self.has_col('report_params.type')
+
+    def upgrade(c):
+        c('ALTER TABLE report_params ADD COLUMN type TEXT')
+        c("UPDATE report_params SET type='line'")
+        c('ALTER TABLE units DROP password')
+
+
+class _UG(Upgrades):
+    """
+    1.4.6: last_update on users, persons, cases [3943], [3944]
+    """
+
+    def test(self):
+        return self.has_col('users.last_update')
+
+    def upgrade(c):
+        c('ALTER TABLE users ADD COLUMN last_update TIMESTAMP')
+        c('ALTER TABLE persons ADD COLUMN last_update TIMESTAMP')
+        c('ALTER TABLE cases ADD COLUMN last_update TIMESTAMP')
+        c('ALTER TABLE cases ADD COLUMN delete_reason TEXT')
+        c('ALTER TABLE cases ADD COLUMN delete_timestamp TIMESTAMP')
+
+
+class _UG(Upgrades):
+    """
+    1.5.0: logical form delete 
+    """
+    def test(self):
+        return self.has_col('case_form_summary.deleted')
+
+    def upgrade(c):
+        c('ALTER TABLE case_form_summary ADD COLUMN deleted BOOLEAN')
+        c('UPDATE case_form_summary SET deleted=false')
+        c('ALTER TABLE case_form_summary ALTER deleted SET DEFAULT false')
+        c('ALTER TABLE case_form_summary ADD COLUMN delete_reason TEXT')
+        c('ALTER TABLE case_form_summary ADD COLUMN delete_timestamp TIMESTAMP')
+
+
+class _UG(Upgrades):
+    """
+    1.5.0: master/contact case relationship replaced with bidirectional
+    association.
+
+    *** WARNING *** 
+    case/contact relationship is now many-to-many, and contact syndromes
+    (case definitions) have been deprecated in favour of using a single
+    syndrome (case definition) for both cases and contacts, with "contact
+    only" status indicated via case status.
+    """
+
+    def test(self):
+        return self.has_relation('case_contacts')
+
+    def upgrade(c):
+        c('CREATE TABLE contact_types ('
+          '  contact_type_id SERIAL PRIMARY KEY,'
+          '  contact_type VARCHAR'
+          ') WITH OIDS')
+        c('CREATE TABLE case_contacts ('
+          '  case_id INT REFERENCES cases(case_id)'
+          '         ON UPDATE cascade ON DELETE cascade,'
+          '  contact_id INT REFERENCES cases(case_id)'
+          '         ON UPDATE cascade ON DELETE cascade,'
+          '  contact_type_id INT REFERENCES contact_types(contact_type_id)'
+          '         ON DELETE SET NULL,'
+          '  contact_date TIMESTAMP,'
+          '  PRIMARY KEY (case_id, contact_id)'
+          ') WITH OIDS')
+        # Duplicate case ACL to contacts
+        c('INSERT INTO case_acl (master_id, unit_id)'
+          '    SELECT case_id, unit_id FROM case_acl'
+          '        JOIN cases USING (master_id)'
+          '        WHERE master_id != case_id')
+        c('ALTER TABLE case_acl RENAME master_id TO case_id')
+        # Copy master_id<>case_id relationship 
+        c('INSERT INTO case_contacts (case_id, contact_id)'
+          '    SELECT master_id, case_id FROM cases'
+          '        WHERE master_id != case_id')
+        c('INSERT INTO case_contacts (contact_id, case_id)'
+          '    SELECT master_id, case_id FROM cases'
+          '        WHERE master_id != case_id')
+        # Remap contact task actions
+        c('UPDATE tasks SET action=action-4 WHERE action IN (5,6,7,8)')
+        # Remove redundant columns
+        c('ALTER TABLE cases DROP master_id')
+        c('ALTER TABLE cases DROP master_syndrome_id')
+        c('ALTER TABLE tasks DROP master_id')
+        c('ALTER TABLE user_log DROP master_id')
+        # Remove redundant tables
+        c('DROP TABLE contact_syndromes')
+
+
+class _UG(Upgrades):
+    """
+    1.5.0: casesets
+    """
+
+    def test(self):
+        return self.has_relation('casesets')
+
+    def upgrade(c):
+        c('CREATE TABLE casesets ('
+          '  caseset_id SERIAL PRIMARY KEY,'
+          '  name VARCHAR,'
+          '  dynamic BOOLEAN,'
+          '  unit_id INTEGER REFERENCES units(unit_id) ON DELETE cascade,'
+          '  user_id INTEGER REFERENCES users(user_id) ON DELETE cascade,'
+          '  pickle BYTEA'
+          ') WITH OIDS')
+
+
+class _UG(Upgrades):
+    """
+    1.5.0: case tags (exposures)
+    """
+    def test(self):
+        return self.has_relation('tags')
+
+    def upgrade(c):
+        c('CREATE TABLE tags ('
+          '  tag_id SERIAL PRIMARY KEY,'
+          '  tag VARCHAR,'
+          '  notes VARCHAR'
+          ') WITH OIDS')
+        c('CREATE TABLE case_tags ('
+          '  case_tag_id SERIAL PRIMARY KEY,'
+          '  case_id INT REFERENCES cases(case_id)'
+          '         ON UPDATE cascade ON DELETE cascade,'
+          '  tag_id INT REFERENCES tags(tag_id)'
+          '         ON UPDATE cascade ON DELETE cascade'
+          ') WITH OIDS')
+        c('ALTER TABLE users ALTER password TYPE VARCHAR')
+
+
+class _UG(Upgrades):
+    """
+    1.5.0: new user sponsorship
+    """
+    def test(self):
+        return self.has_col('users.sponsoring_user_id')
+
+    def upgrade(c):
+        c('ALTER TABLE users ADD COLUMN sponsoring_user_id INTEGER'
+          ' REFERENCES users(user_id) ON DELETE SET NULL')
+        c('ALTER TABLE users ADD COLUMN enable_key VARCHAR')
+
+class _UG(Upgrades):
+    """
+    1.5.0: remove per-syndrome address state
+    """
+    def test(self):
+        return not self.has_col('address_states.syndrome_id')
+
+    def upgrade(c):
+        if c.agg('SELECT count(*) FROM address_states'
+                 ' WHERE syndrome_id IS NOT NULL'):
+            c.abort('per-syndrome address states have been used.')
+        c('ALTER TABLE address_states DROP COLUMN syndrome_id')
+
+class _UG(Upgrades):
+    """
+    1.5.0: add case assignment
+    """
+    def test(self):
+        return self.has_relation('syndrome_case_assignments')
+
+    def upgrade(c):
+        c('ALTER TABLE cases ADD COLUMN case_assignment TEXT')
+        c('CREATE TABLE syndrome_case_assignments ('
+          '    syndca_id SERIAL PRIMARY KEY,'
+          '    syndrome_id INT REFERENCES syndrome_types ON DELETE CASCADE,'
+          '    name TEXT,'
+          '    label TEXT'
+          ') WITH OIDS')
+
+class _UG(Upgrades):
+    """
+    1.7.0: report sharing & XML parameters
+    """
+
+    def test(self):
+        return self.has_col('report_params.sharing')
+
+    def upgrade(c):
+        from report_1_6.convert import report_cvt
+        c('ALTER TABLE report_params ADD COLUMN sharing TEXT')
+        c('ALTER TABLE report_params ADD COLUMN xmldef TEXT')
+        c('CREATE INDEX rp_type_idx ON report_params (type)')
+        c('CREATE INDEX rp_sharing_idx ON report_params (sharing)')
+        c('CREATE INDEX rp_unit_idx ON report_params (unit_id)')
+        c('CREATE INDEX rp_user_idx ON report_params (user_id)')
+        report_cvt(c.db)
+        c('ALTER TABLE report_params DROP COLUMN pickle')
+
+class _UG(Upgrades):
+    """
+    1.7.0: enhanced DOB storage (DOB + precision)
+    """
+
+    def test(self):
+        return self.has_col('persons.DOB_prec')
+
+    def upgrade(c):
+        c('ALTER TABLE persons ADD COLUMN DOB_prec INTEGER')
+        c('ALTER TABLE persons ALTER COLUMN DOB_prec SET DEFAULT 0')
+        c('UPDATE persons SET DOB_prec = '
+            'CASE WHEN dob_is_approx THEN 366 ELSE 0 END')
+        c('ALTER TABLE persons DROP COLUMN DOB_is_approx')
diff --git a/casemgr/search.py b/casemgr/search.py
new file mode 100644
index 0000000..c05d29c
--- /dev/null
+++ b/casemgr/search.py
@@ -0,0 +1,204 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+
+from cocklebur import dbobj, utils, datetime, checkdigit
+from casemgr import paged_search, person, fuzzyperson, caseaccess, \
+                    demogfields, globals, resultpersons, casetags
+import config
+
+class SearchError(dbobj.DatabaseError): pass
+
+class ResultForm:
+    result_type = 'form'
+
+    def __init__(self, summary_id):
+        self.summary_id = summary_id
+
+
+class SearchOrderMixin:
+
+    orders  = [
+        'surname,given_names',
+        'given_names,surname',
+        'onset_datetime,surname,given_names',
+        'notification_datetime,surname,given_names',
+        'case_id',
+        'local_case_id,surname,given_names',
+        'postcode,surname,given_names',
+        'DOB,surname,given_names',
+        'locality,surname,given_names',
+    ]
+    default_order = orders[0]
+
+    def get_order_options(self):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        options = []
+        for order in self.orders:
+            names = order.split(',')
+            try:
+                field = fields.field_by_name(names[0])
+            except LookupError:
+                continue
+            options.append((order, field.label))
+        return options
+
+    def order_by_cols(self):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        cols = []
+        for name in self.order_by.split(','):
+            try:
+                field = fields.field_by_name(name)
+            except LookupError:
+                continue
+            cols.append(field.name)
+        return cols
+
+
+class Search(SearchOrderMixin):
+
+    persons_per_page_options = [10, 25, 50]
+    saved = None
+
+    def __init__(self, search_ops):
+        self.search_ops = search_ops
+        # Note that syndrome_id can be None, in which case a cross-syndrome
+        # search is performed. The user can still (potentially) restrict the
+        # syndrome via the search parameters (search_syndrome_id).
+        self.syndrome_id = self.search_ops.syndrome_id
+        self.search_syndrome_id = self.syndrome_id
+        self.description = None
+        self.get_prefs()
+        self.tabs = self.get_demog_fields().tabs()
+        self.reset()
+
+    def get_prefs(self):
+        self.fuzzy = self.search_ops.prefs.get('phonetic_search')
+        self.persons_per_page = self.search_ops.prefs.get('persons_per_page')
+        self.order_by = self.search_ops.prefs.get('persons_order')
+        if self.order_by is None:
+            self.order_by = self.default_order
+
+    def save_prefs(self):
+        prefs = self.search_ops.prefs
+        prefs.set_from_str('persons_per_page', self.persons_per_page)
+        prefs.set('phonetic_search', str(self.fuzzy) == 'True')
+        prefs.set('persons_order', self.order_by)
+
+    def reset(self):
+        self.person = person.person()
+        self.quicksearch = self.local_case_id = self.notifier_name = ''
+        self.reverse = False
+        self.case_status = ''
+        self.case_assignment = ''
+        self.tags = ''
+        self.deleted = 'n'
+        self.description = None
+        self.result = None
+
+    case_id_re = re.compile('\d+$')
+    form_id_re = re.compile('F\d+$')
+
+    def _search_quick(self, query, ns):
+        caseaccess.acl_query(query, self.search_ops.cred, deleted=None)
+        or_expr = query.sub_expr('OR')
+        for substr in utils.commasplit(self.quicksearch):
+            if self.case_id_re.match(substr):
+                or_expr.where('case_id = %s', int(substr))
+            elif dbobj.is_wild(substr):
+                substr = dbobj.wild(substr)
+                or_expr.where("(given_names||' '||surname) ILIKE %s", substr)
+            else:
+                and_expr = or_expr.sub_expr('AND')
+                and_expr.where('not deleted')
+                fuzzyperson.find(and_expr, *substr.split())
+            or_expr.where('local_case_id ILIKE %s', substr)
+        return 'Quick search: %s' % self.quicksearch
+
+    def _search_detail(self, query, ns):
+        caseaccess.acl_query(query, self.search_ops.cred, deleted=self.deleted)
+        if self.case_status:
+            if self.case_status == '!':
+                query.where('cases.case_status IS null')
+            else:
+                query.where('cases.case_status = %s', self.case_status)
+        if self.case_assignment:
+            if self.case_assignment == '!':
+                query.where('cases.case_assignment IS null')
+            else:
+                query.where('cases.case_assignment = %s', self.case_assignment)
+        if self.local_case_id:
+            query.where('cases.local_case_id ILIKE %s', 
+                        dbobj.wild(self.local_case_id))
+        if self.notifier_name:
+            query.where('cases.notifier_name ILIKE %s', 
+                        dbobj.wild(self.notifier_name))
+        if self.tags:
+            for tag in casetags.tags_from_str(self.tags):
+                subq = query.in_select('case_id', 'case_tags')
+                subq.join('JOIN tags USING (tag_id)')
+                subq.where('tag = %s', tag)
+        try:
+            self.person.to_query(globals.db, query, self.fuzzy)
+        except person.Error, e:
+            raise SearchError(str(e))
+        description = ''
+        if ns is not None:
+            summary = self.get_demog_fields().summary(ns)
+            if summary:
+               description = 'Terms: %s' % summary
+        return description
+
+    def search(self, ns=None):
+        """
+        First we build a set of candidate persons by searching
+        cases and contacts, then we run the person search terms
+        across that.
+        """
+        if self.quicksearch and self.form_id_re.match(self.quicksearch):
+            id_okay, id = checkdigit.check_checkdigit(self.quicksearch[1:])
+            if not id_okay:
+                raise SearchError('Invalid form ID: %s', self.quicksearch)
+            self.result = ResultForm(id)
+            return
+        order_by = self.order_by_cols()
+        if self.reverse:
+            order_by = [col + ' DESC' for col in order_by]
+        order_by.append('notification_datetime')
+        query = globals.db.query('persons', order_by=order_by)
+        query.join('JOIN cases USING (person_id)')
+        if (self.search_syndrome_id is not None and 
+                not self.search_ops.all_syndrome_result and
+                self.search_syndrome_id != 'Any'):
+            query.where('syndrome_id = %s', int(self.syndrome_id))
+        if self.quicksearch:
+            description = self._search_quick(query, ns)
+        else:
+            description = self._search_detail(query, ns)
+        self.result = resultpersons.ResultPersons(
+            self.search_ops, query,
+            initial_cols=self.order_by_cols(),
+            description=description)
+
+    def get_demog_fields(self):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        return fields.context_fields(self.search_ops.context)
+
+    def field_label(self, name):
+        fields = demogfields.get_demog_fields(globals.db, self.syndrome_id)
+        return fields.field_by_name(name).label
diff --git a/casemgr/sendmail.py b/casemgr/sendmail.py
new file mode 100644
index 0000000..bd7a270
--- /dev/null
+++ b/casemgr/sendmail.py
@@ -0,0 +1,86 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+import re
+import sys
+from email import Message, Utils, Errors
+
+value_addr_re = re.compile(r'^[a-z][a-z0-9_.-]*@([a-z_-]+\.)*[a-z_-]+$',
+                           re.IGNORECASE)
+def valid_addr(addr):
+    if not value_addr_re.match(addr):
+        raise Errors.MessageError('invalid address %r' % addr)
+
+class Sendmail:
+    """
+    A proxy that knows how to invoke sendmail for
+    email.Message.Message-like classes.
+    """
+    sm_path_try = (
+        '/usr/lib/sendmail',
+        '/usr/sbin/sendmail',
+    )
+
+    def __init__(self, message_ctor = None, *args, **kwargs):
+        if message_ctor is None:
+            message_ctor = Message.Message
+        self.message = message_ctor(*args, **kwargs)
+
+    def __getattr__(self, a):
+        return getattr(self.message, a)
+
+    def getaddrs(self, *hdrs):
+        addrs = []
+        for hdr in hdrs:
+            for cmt, addr in Utils.getaddresses(self.get_all(hdr, [])):
+                try:
+                    valid_addr(addr)
+                except Errors.MessageError, e:
+                    raise Errors.MessageError('%s: %s' % (hdr, e))
+                addrs.append(addr)
+        return addrs
+
+    def send(self):
+        sender = self.getaddrs('from')
+        if sender:
+            if len(sender) > 1:
+                raise Errors.MessageError('More than one sender specified: %s' %
+                                          (', '.join(sender)))
+            else:
+                sender = sender[0]
+        recipients = self.getaddrs('to', 'cc')
+        if not recipients:
+            raise ValueError('no recipients specified')
+        if not self.has_key('subject'):
+            self.add_header('subject', ' '.join(sys.argv))
+        for sm_path in self.sm_path_try:
+            if os.path.exists(sm_path):
+                sendmail = sm_path
+                break
+        else:
+            raise ValueError('sendmail not found')
+        args = [sendmail]
+        if sender:
+            args.append('-f' + sender)
+        args.extend(recipients)
+        sm = os.popen(' '.join(args), 'w')
+        try:
+            sm.write(self.as_string(unixfrom = False))
+        finally:
+            if sm.close():
+                print >> sys.stderr, 'command failed: %s' % ' '.join(args)
diff --git a/casemgr/svnrev.py b/casemgr/svnrev.py
new file mode 100644
index 0000000..6a03f9b
--- /dev/null
+++ b/casemgr/svnrev.py
@@ -0,0 +1 @@
+__svnrev__ = '4432'
diff --git a/casemgr/syndcategorical.py b/casemgr/syndcategorical.py
new file mode 100644
index 0000000..5342f46
--- /dev/null
+++ b/casemgr/syndcategorical.py
@@ -0,0 +1,208 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Base classes for syndrome-specific case fields such as "case status"
+
+"""
+
+from casemgr import globals, mergelabels, cached
+
+
+unknown_state = ('', 'Unknown')
+
+class SyndromeCategoricalInfo(object):
+
+    table = None
+    order_by = None
+    explicit_order = False
+    defaults = []
+
+
+class EditSyndromeCategorical(object):
+    """
+    Table must have "name" and "label" columns.
+
+    """
+
+    def __init__(self, syndrome_id):
+        """
+        syndrome_id is None to edit the default values
+        """
+        self.syndrome_id = syndrome_id
+        self.order = ''
+        query = globals.db.query(self.table, order_by=self.order_by)
+        if self.syndrome_id is None:
+            query.where('syndrome_id IS NULL')
+        else:
+            query.where('syndrome_id = %s', self.syndrome_id)
+        self.rows = query.fetchall()
+        if not self.rows:
+            if self.syndrome_id is not None:
+                query = globals.db.query(self.table, 
+                                         order_by=self.order_by)
+                query.where('syndrome_id IS NULL')
+                for row in query.fetchall():
+                    self.new(name=row.name, label=row.label)
+            if not self.rows:
+                for name, label in self.defaults:
+                    self.new(name=name, label=label)
+
+    def __len__(self):
+        return len(self.rows)
+
+    def new(self, **kwargs): 
+        if self.explicit_order:
+            id = globals.db.nextval(self.table, self.order_by)
+            kwargs[self.order_by] = id
+        row = self.rows.new_row(syndrome_id=self.syndrome_id, **kwargs)
+        self.rows.append(row)
+        return row
+
+    def delete(self, index):
+        del self.rows[index]
+
+    def swap(self, a, b):
+        assert self.explicit_order
+        length = len(self.rows)
+        if 0 <= a < length and 0 <= b < length:
+            tmp = self.rows[a].name, self.rows[a].label
+            self.rows[a].name, self.rows[a].label = \
+                self.rows[b].name, self.rows[b].label
+            self.rows[b].name, self.rows[b].label = tmp
+
+    def reorder(self):
+        if self.order:
+            nl = [(row.name, row.label) for row in self.rows]
+            for dst, src in enumerate(self.order.split(',')):
+                self.rows[dst].name, self.rows[dst].label = nl[int(src)]
+            self.order = ''
+
+    def move_up(self, index):
+        self.swap(index, index - 1)
+
+    def move_down(self, index):
+        self.swap(index, index + 1)
+
+    def __getitem__(self, index):
+        return self.rows[index]
+
+    def has_changed(self):
+        return self.rows.db_has_changed()
+
+    def update(self):
+        self.rows.db_update()
+
+
+class SyndromeValues(list):
+
+    def __init__(self, initial=None):
+        self.by_name = {}
+        if initial is not None:
+            for row in initial:
+                self.add(*row)
+
+    def add(self, name, label):
+        pair = name, label
+        self.append(pair)
+        self.by_name[name.lower()] = pair
+
+    def __contains__(self, name):
+        return name.lower() in self.by_name
+
+    def label(self, name):
+        if name:
+            try:
+                return self.by_name[name.lower()][1]
+            except KeyError:
+                pass
+        return unknown_state[1]
+
+    def normalise(self, name):
+        if name:
+            try:
+                return self.by_name[name.lower()][0]
+            except KeyError:
+                pass
+        return unknown_state[0]
+
+
+class SyndromeCategorical(cached.NotifyCache):
+
+    notification_target = 'syndromes'
+
+    def load(self):
+        by_syndrome = {}
+        allvals = mergelabels.MergeLabels()
+        allvals.add(*unknown_state)
+        query = globals.db.query(self.table, order_by=self.order_by)
+        query.join('LEFT JOIN syndrome_types USING (syndrome_id)')
+        query.where('syndrome_id IS NULL OR syndrome_types.enabled')
+        for row in query.fetchall():
+            try:
+                syndvals = by_syndrome[row.syndrome_id]
+            except KeyError:
+                syndvals = by_syndrome[row.syndrome_id] = SyndromeValues()
+                syndvals.add(*unknown_state)
+            syndvals.add(row.name, row.label)
+            allvals.add(row.name, row.label)
+        if None not in by_syndrome:
+            syndvals = by_syndrome[None] = SyndromeValues()
+            syndvals.add(*unknown_state)
+            for name, label in self.defaults:
+                syndvals.add(name, label)
+                allvals.add(name, label)
+        self.by_syndrome = by_syndrome
+        self.allvals = SyndromeValues(allvals.in_order())
+
+    def get_syndrome(self, syndrome_id):
+        self.refresh()
+        if syndrome_id is None:
+            return self.allvals
+        try:
+            return self.by_syndrome[syndrome_id]
+        except KeyError:
+            return self.by_syndrome[None]
+
+    def get_common(self):
+        self.refresh()
+        return self.allvals
+
+    def optionexpr(self, *syndrome_ids):
+        if not syndrome_ids or syndrome_ids[0] is None:
+            return self.get_common()
+        elif len(syndrome_ids) == 1:
+            return self.get_syndrome(syndrome_ids[0])
+        else:
+            merged = mergelabels.MergeLabels()
+            merged.add(*unknown_state)
+            for syndrome_id in syndrome_ids:
+                merged.addall(self.get_syndrome(syndrome_id))
+            return merged.in_order()
+
+    def get_label(self, syndrome_id, name):
+        """
+        Given a syndrome and name, return associated label
+        """
+        return self.get_syndrome(syndrome_id).label(name)
+
+    def normalise(self, syndrome_id, name):
+        """
+        Given a syndrome name, return canonical form
+        """
+        return self.get_syndrome(syndrome_id).normalise(name)
diff --git a/casemgr/syndrome.py b/casemgr/syndrome.py
new file mode 100644
index 0000000..8a3365f
--- /dev/null
+++ b/casemgr/syndrome.py
@@ -0,0 +1,346 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+import time
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj, datetime
+import config
+from casemgr import globals, cached
+
+def query_by_group(group_id):
+    query = globals.db.query('syndrome_types', 
+                             order_by=config.order_syndromes_by)
+    query.where('enabled')
+    synd_query = query.in_select('syndrome_id', 'group_syndromes')
+    synd_query.where('group_id = %s', group_id)
+    cont_query = synd_query.in_select('syndrome_id', 'group_syndromes')
+    cont_query.where('group_id = %s', group_id)
+    return query
+
+
+class SyndFormInfo(object):
+    __slots__ = 'name', 'label', 'version'
+
+    def __init__(self, name, label, version):
+        self.name = name
+        self.label = label
+        self.version = version
+
+    def __getinitargs__(self):
+        return self.name, self.label, self.version
+
+    def load(self):
+        return globals.formlib.load(self.name, self.version)
+
+    def tablename(self):
+        return globals.formlib.tablename(self.name, self.version)
+
+
+class Syndrome(object):
+    def __init__(self, syndrome_id):
+        self.syndrome_id = syndrome_id
+        self.case_count = None
+
+    def update(self, name, description, priority, enabled, 
+               post_date, expiry_date,
+               has_additional_info):
+        self.name = name
+        self.description = description
+        self.priority = priority
+        self.enabled = enabled
+        self.post_date = datetime.mx_parse_datetime(post_date)
+        self.expiry_date = datetime.mx_parse_datetime(expiry_date)
+        self.has_additional_info = has_additional_info
+        self.forms = None
+        self.forms_by_name = None
+
+    def active(self):
+        now = datetime.now()
+        return (self.enabled 
+                and (not self.post_date or self.post_date <= now)
+                and (not self.expiry_date or self.expiry_date > now))
+
+    def getrow(self):
+        query = globals.db.query('syndrome_types')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        return query.fetchone()
+
+    def load_synd_forms(self):
+        query = globals.db.query('syndrome_forms', order_by='name')
+        query.join('JOIN forms ON (form_label = label)')
+        query.where('syndrome_id = %s', self.syndrome_id)
+        self.forms = []
+        self.forms_by_name = {}
+        for row in query.fetchcols(('form_label', 'name', 'cur_version')):
+            info = SyndFormInfo(*row)
+            self.forms.append(info)
+            self.forms_by_name[info.name.lower()] = info
+
+    def all_form_info(self):
+        if self.forms is None:
+            self.load_synd_forms()
+        return self.forms
+
+    def form_info(self, name):
+        if self.forms_by_name is None:
+            self.load_synd_forms()
+        return self.forms_by_name.get(name.lower())
+
+    def desc_status(self):
+        res = []
+        if self.active():
+            res.append('active')
+        else:
+            if self.enabled:
+                res.append('disabled by post date or expiry date')
+            else:
+                res.append('disabled administratively')
+        if self.case_count == 1:
+            res.append('1 record')
+        elif self.case_count:
+            res.append('%d records' % self.case_count)
+        return res
+
+    def __reduce__(self):
+        # No point pickling the whole object, just the constructor and ID
+        return _syndrome_reduce, (self.syndrome_id,)
+
+def _syndrome_reduce(syndrome_id):
+    return syndromes[syndrome_id]
+
+
+class CaseCountMixin(object):
+    def __init__(self):
+        self.__want_refresh = set()
+        self.__syndrome_ids = set()
+        self.__refresh_time = time.time()
+        globals.notify.subscribe('syndromecasecount',
+                                 self.__notification)
+
+    def __notification(self, *args):
+        self.__want_refresh.update(map(int, args))
+
+    def refresh(self):
+        now = time.time()
+        all = set(self.syndrome_ids())
+        seen = self.__syndrome_ids
+        if self.__refresh_time + 120 < now:
+            seen = set()
+        # Any new syndromes, plus ones with new cases
+        refresh = all - seen | self.__want_refresh
+        self.__syndrome_ids = all
+        if refresh:
+            self.__refresh_time = now
+            query = globals.db.query('cases', group_by='syndrome_id')
+            query.where('not deleted')
+            query.where_in('syndrome_id', refresh)
+            cols = 'syndrome_id', 'count(*)'
+            for syndrome_id, count in query.fetchcols(cols):
+                try:
+                    synd = self.by_id[syndrome_id]
+                except KeyError:
+                    pass
+                else:
+                    synd.case_count = count
+            self.__want_refresh = set()
+
+
+def membership_sets(table, group_col, member_col):
+    membership_by_group = {}
+    query = globals.db.query(table)
+    for group, member in query.fetchcols((group_col, member_col)):
+        try:
+            membership = membership_by_group[group]
+        except KeyError:
+            membership = membership_by_group[group] = set()
+        membership.add(member)
+    return membership_by_group
+
+
+class UnitSyndromes(cached.NotifyCache):
+
+    notification_target = 'syndrome_units'
+
+    def __init__(self):
+        cached.NotifyCache.__init__(self)
+
+    def load(self):
+        syndromes_by_group = membership_sets('group_syndromes', 
+                                             'group_id', 'syndrome_id')
+        query = globals.db.query('unit_groups')
+        unit_syndromes_by_unit = {}
+        for unit_id, group_id in query.fetchcols(('unit_id', 'group_id')):
+            group_syndromes = syndromes_by_group.get(group_id)
+            if group_syndromes:
+                try:
+                    unit_syndromes = unit_syndromes_by_unit[unit_id]
+                except KeyError:
+                    unit_syndromes_by_unit[unit_id] = group_syndromes
+                else:
+                    unit_syndromes = unit_syndromes | group_syndromes
+                    unit_syndromes_by_unit[unit_id] = unit_syndromes
+        self.unit_syndromes = unit_syndromes_by_unit
+
+    def get_unit_syndromes(self, unit_id):
+        self.refresh()
+        return self.unit_syndromes.get(unit_id, ())
+
+unit_syndromes = UnitSyndromes()
+
+
+class Syndromes(cached.NotifyCache, CaseCountMixin):
+
+    notification_target = 'syndromes'
+
+    def __init__(self):
+        cached.NotifyCache.__init__(self)
+        CaseCountMixin.__init__(self)
+        self.by_id = {}
+        self.in_order = []
+        self.active_in_order = []
+
+    def refresh(self):
+        cached.NotifyCache.refresh(self)
+        CaseCountMixin.refresh(self)
+
+    def load(self):
+        query = globals.db.query('syndrome_types', 
+                                 order_by=config.order_syndromes_by)
+        by_id = {}
+        in_order = []
+        active_in_order = []
+        cols = (
+            'syndrome_id', 'name', 'description', 
+            'priority', 'enabled', 'post_date', 'expiry_date',
+            'additional_info is not null'
+        )
+        for row in query.fetchcols(cols):
+            syndrome_id = row[0]
+            try:
+                synd = self.by_id[syndrome_id]
+            except KeyError:
+                synd = Syndrome(syndrome_id)
+            synd.update(*row[1:])
+            by_id[syndrome_id] = synd
+            in_order.append(synd)
+            if synd.active():
+                active_in_order.append(synd)
+        # We defer setting attributes until the last moment so if an error
+        # occurs, the previous state of the object is still usable.
+        self.by_id = by_id
+        self.in_order = in_order
+        self.active_in_order = active_in_order
+
+    def syndrome_ids(self):
+        return [s.syndrome_id for s in self.in_order]
+
+    def __getitem__(self, index):
+        self.refresh()
+        try:
+            return self.by_id[int(index)]
+        except (LookupError, TypeError, ValueError):
+            raise LookupError('Invalid %s id %r' % (config.syndrome_label, index))
+
+    def all(self):
+        self.refresh()
+        return self.in_order
+
+    def __contains__(self, syndrome_id):
+        self.refresh()
+        return syndrome_id in self.by_id
+
+    def __iter__(self):
+        self.refresh()
+        return iter(self.active_in_order)
+
+    def __nonzero__(self):
+        self.refresh()
+        return len(self.active_in_order) > 0
+
+    def __getstate__(self):
+        # Prevent pickling - force people to use module-level instance
+        raise TypeError
+
+    def optionexpr(self):
+        return [(s.syndrome_id, s.name) for s in self.all()]
+
+
+#    def __reduce__(self):
+#        # Ensure pickle uses module level instance
+#        return _syndromes_reduce
+#
+#def _syndromes_reduce():
+#    return syndromes
+
+syndromes = Syndromes()
+
+class UnitSyndromesView:
+    """
+    A view of the global /syndromes/, including only syndromes the user
+    has the rights to see.
+    """
+
+    def __init__(self, credentials):
+        self.credentials = credentials
+
+    def _unit_syndromes(self):
+        return unit_syndromes.get_unit_syndromes(self.credentials.unit.unit_id)
+
+    def __getitem__(self, syndrome_id):
+        return syndromes[syndrome_id]
+
+    def __nonzero__(self):
+        if config.show_all_syndromes:
+            return bool(syndromes)
+        else:
+            us = self._unit_syndromes()
+            for synd in syndromes:
+                if synd.syndrome_id in us:
+                    return True
+            return False
+
+    def __contains__(self, syndrome_id):
+        if config.show_all_syndromes:
+            return syndrome_id in syndromes
+        else:
+            return syndrome_id in self._unit_syndromes()
+
+    def __iter__(self):
+        if config.show_all_syndromes:
+            for synd in syndromes:
+                yield synd
+        else:
+            us = self._unit_syndromes()
+            for synd in syndromes:
+                if synd.syndrome_id in us:
+                    yield synd
+
+    def options(self):
+        return [(s.syndrome_id, s.name) for s in self]
+
+    def anyoptions(self):
+        options = self.options()
+        options.insert(0, ('Any', 'Any'))
+        return options
+
+    def can_add(self, syndrome_id):
+        return syndrome_id in self
diff --git a/casemgr/tabs.py b/casemgr/tabs.py
new file mode 100644
index 0000000..c6923ea
--- /dev/null
+++ b/casemgr/tabs.py
@@ -0,0 +1,105 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+class Spacer(object):
+    spacer = True
+
+class Tab(object):
+    spacer = False
+
+    def __init__(self, tabs, name, label, 
+                 action=False, danger=True, accesskey=None):
+        self.tabs = tabs
+        self.name = name
+        self.label = label
+        self.action = action
+        self.danger = danger
+        self.accesskey = accesskey
+        if self.action:
+            self.nameexpr = self.name
+        else:
+            self.nameexpr = 'tab:' + self.name
+
+    def selected(self):
+        return self.name == self.tabs.selected
+
+    def css_class(self):
+        style = 'tab'
+        if self.danger:
+            style += ' danger'
+        if self.action:
+            style += ' act'
+        if self.name == self.tabs.selected:
+            style += ' selected'
+        return style
+
+
+class Tabs(list):
+    def __init__(self, initial=None):
+        self.accesskeys = {}
+        self.selected = initial
+        self.width = None
+
+    def add(self, name, label, action=False, danger=False, accesskey=None):
+        """
+        An "action" is a button that leaves the context of the tab system
+        """
+        self.append(Tab(self, name, label, action, danger, accesskey))
+
+    def spacer(self):
+        self.append(Spacer())
+
+    def select(self, name):
+        """
+        Attempt to select the requested tab. If not found, select the
+        first tab.
+        """
+        first = None
+        for tab in self:
+            if not tab.spacer and tab.name:
+                if first is None:
+                    first = tab.name
+                if tab.name == name:
+                    break
+        else:
+            name = first
+        self.selected = name
+
+    def done(self):
+        self.select(self.selected)      # Force valid selection
+        if self:
+            widths = []
+            for tab in self:
+                if not tab.spacer:
+                    widths.append(len(tab.label))
+                    if tab.accesskey is None:
+                        for l in tab.label:
+                            l = l.lower()
+                            if l.isalpha() and l not in self.accesskeys:
+                                self.accesskeys[l] = tab
+                                tab.accesskey = l
+                                break
+            self.width = max(widths)
+        return self
+
+    def css_width(self):
+        style = []
+        if self.width:
+            # Only an approximation with proportional width fonts
+            style.append('width: %.1fem;' % (self.width * .6))
+        return ''.join(style)
diff --git a/casemgr/taskdesc.py b/casemgr/taskdesc.py
new file mode 100644
index 0000000..67175fd
--- /dev/null
+++ b/casemgr/taskdesc.py
@@ -0,0 +1,107 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, cached
+
+class TaskDescError(globals.Error): pass
+
+class TaskDescriptions(cached.NotifyCache):
+    """
+    This class "learns" what task parameters are typically set by a
+    given task_description.
+    """
+    cols = (
+        'queue_id', 'creation_date', 'active_date', 'due_date', 
+        'task_description', 'annotation', 'form_name'
+    )
+    notification_target = 'taskdesc'
+
+    def __init__(self, syndrome_id):
+        cached.NotifyCache.__init__(self)
+        self.syndrome_id = syndrome_id
+
+    def load(self):
+        query = globals.db.query('tasks', limit=200, order_by='task_id desc')
+        if self.syndrome_id is None:
+            query.where('case_id IS null')
+        else:
+            query.join('LEFT JOIN cases USING (case_id)')
+            query.where('syndrome_id = %s', self.syndrome_id)
+        desc_kv_counts = {}
+        desc_count = {}
+        for row in query.fetchcols(self.cols):
+            row = dict(zip(self.cols, row))
+            creation_date = row.pop('creation_date')
+            active_date = row.pop('active_date')
+            active_relative = active_date - creation_date
+            if active_relative >= 0:
+                row['active_date'] = active_relative
+            if row['due_date']:
+                due_relative = row['due_date'] - active_date
+                if due_relative >= 0:
+                    row['due_date'] = due_relative
+            desc_key = row['task_description']
+            desc_count[desc_key] = desc_count.get(desc_key, 0) + 1
+            kv_counts = desc_kv_counts.setdefault(desc_key, {})
+            for key, value in row.items():
+                vc = kv_counts.setdefault(key, {})
+                vc[value] = vc.get(value, 0) + 1
+        desc_kv_counts = [(desc_count[desc], desc, kv_counts)
+                          for desc, kv_counts in desc_kv_counts.iteritems()]
+        desc_kv_counts.sort()
+        desc_kv_counts.reverse()
+        del desc_kv_counts[20:]
+        names_order = []
+        desc_params_by_name = {}
+        for row_count, desc, kv_counts in desc_kv_counts:
+            desc_params = {}
+            for key, vc in kv_counts.iteritems():
+                vc = [(c, i, v) for i, (v, c) in enumerate(vc.iteritems())]
+                vc.sort()
+                top_count, ignore, value = vc[-1]
+                if float(top_count) / float(row_count) > 0.5:
+                    desc_params[key] = value
+            names_order.append(desc)
+            desc_params_by_name[desc] = desc_params
+        self.names_order = names_order
+        self.desc_params_by_name = desc_params_by_name
+
+    def options(self):
+        return self.names_order
+
+    def params(self, name):
+        try:
+            return self.desc_params_by_name[name]
+        except KeyError:
+            raise TaskDescError('Task description %r not available' % name)
+
+task_desc_by_syndrome = {}
+
+def get_task_descs(syndrome_id):
+    try:
+        td = task_desc_by_syndrome[syndrome_id]
+    except KeyError:
+        td = task_desc_by_syndrome[syndrome_id] = TaskDescriptions(syndrome_id)
+    td.refresh()
+    return td
+
+def task_options(syndrome_id):
+    return get_task_descs(syndrome_id).options()
+
+def params(syndrome_id, name):
+    return get_task_descs(syndrome_id).params(name)
diff --git a/casemgr/tasks.py b/casemgr/tasks.py
new file mode 100644
index 0000000..ccea4fc
--- /dev/null
+++ b/casemgr/tasks.py
@@ -0,0 +1,631 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from mx import DateTime
+from cocklebur import dbobj, datetime
+from casemgr import globals, paged_search, unituser, cached
+import config
+
+class TaskError(globals.Error): pass
+
+# Task actions
+ACTION_NOTE = 0
+ACTION_NEW_CASE = 1
+ACTION_UPDATE_CASE = 2
+ACTION_NEW_CASE_FORM = 3
+ACTION_UPDATE_CASE_FORM = 4
+ACTION_THREAD_COMPLETED = 9
+ACTION_THREAD_DELETED = 10
+
+action_desc = {
+    ACTION_NOTE: 'Information',
+    ACTION_NEW_CASE: 'New Case',
+    ACTION_UPDATE_CASE: 'Update Case',
+    ACTION_NEW_CASE_FORM: 'New Case Form',
+    ACTION_UPDATE_CASE_FORM: 'Update Case Form',
+    ACTION_THREAD_COMPLETED: 'Completed',
+    ACTION_THREAD_DELETED: 'Deleted',
+}
+
+action_req_case = set((
+    ACTION_UPDATE_CASE, ACTION_NEW_CASE_FORM, ACTION_UPDATE_CASE_FORM,
+))
+action_req_form_name = set((
+    ACTION_NEW_CASE_FORM, ACTION_UPDATE_CASE_FORM,
+))
+action_req_summary_id = set((
+    ACTION_UPDATE_CASE_FORM,
+))
+action_closed = set((
+    ACTION_THREAD_COMPLETED, ACTION_THREAD_DELETED,
+))
+action_case_edit = set((
+    ACTION_UPDATE_CASE,
+))
+action_form_edit = set((
+    ACTION_NEW_CASE_FORM, ACTION_UPDATE_CASE_FORM,
+))
+action_dispatchable = set((
+    ACTION_NOTE,
+    ACTION_UPDATE_CASE, ACTION_NEW_CASE_FORM, ACTION_UPDATE_CASE_FORM,
+))
+
+
+def guess_action(task):
+    if task.case_id is None:
+        # Ambiguous - ACTION_NOTE or ACTION_NEW_CASE
+        return ACTION_NOTE
+    else:
+        if task.form_name is None:
+            return ACTION_UPDATE_CASE
+        elif task.summary_id is None:
+            return ACTION_NEW_CASE_FORM
+        else:
+            return ACTION_UPDATE_CASE_FORM
+
+
+def same_entity(task, action, case_id, form_name=None, 
+                summary_id=None):
+    """
+    Check if the correct entity has been edited
+    """
+    if 0:
+        import sys
+        print >> sys.stderr, 'task: %r %r %r %r' %\
+            (action_desc[task.action], 
+             task.case_id, task.form_name, task.summary_id)
+        print >> sys.stderr, 'user: %r %r %r %r' %\
+            (action_desc[action], case_id, form_name, summary_id)
+    if task.action != action:
+        return False
+    if action in action_req_case and task.case_id != case_id:
+        return False
+    if action in action_req_form_name and task.form_name != form_name:
+        return False
+    if action in action_req_summary_id and task.summary_id != summary_id:
+        return False
+    return True
+
+class WorkQueues(cached.NotifyCache):
+    notification_target = 'workqueues'
+
+    def __init__(self):
+        cached.NotifyCache.__init__(self)
+        self.queues = None
+        self.by_id = None
+
+    def load(self):
+        query = globals.db.query('workqueues', order_by='name')
+        query.where('user_id is null AND unit_id is null')
+        self.queues = query.fetchall()
+        self.by_id = {}
+        for queue in self.queues:
+            self.by_id[queue.queue_id] = queue
+
+    def __len__(self):
+        self.refresh()
+        return len(self.queues)
+
+    def __iter__(self):
+        self.refresh()
+        return iter(self.queues)
+
+    def __getitem__(self, i):
+        self.refresh()
+        return self.by_id[i]
+
+    def options(self):
+        self.refresh()
+        return [(q.queue_id, q.name) for q in self.queues]
+
+workqueues = WorkQueues()
+
+
+def user_workqueues_query(credentials, **kwargs):
+    """
+    Returns a query for workqueues the user is a member of
+    """
+    query = globals.db.query('workqueues', **kwargs)
+    query.join('LEFT JOIN workqueue_members USING (queue_id)')
+    query.where('workqueues.user_id is null AND workqueues.unit_id is null')
+    if 'ACCESSALL' not in credentials.rights:
+        subquery = query.sub_expr('OR')
+        subquery.where('workqueue_members.unit_id = %s', 
+                        credentials.unit.unit_id)
+        subquery.where('workqueue_members.user_id = %s', 
+                        credentials.user.user_id)
+    return query
+
+
+def user_workqueues(credentials):
+    """
+    Returns a list of workqueues the user is a member of
+    """
+    query = user_workqueues_query(credentials, distinct=True, order_by='name')
+    return query.fetchall()
+
+
+class QueueStats(cached.Cached):
+    def __init__(self, queue_id):
+        self.queue_id = queue_id
+        self.refresh()
+
+    def load(self):
+        query = globals.db.query('tasks')
+        if self.queue_id is not None:
+            query.where('queue_id = %s', self.queue_id)
+        now = datetime.now()
+        self.total = self.completed = self.active =\
+            self.overdue = self.locked = 0
+        cols = 'due_date', 'completed_date', 'locked_by_id'
+        for due_date, completed_date, locked_by_id in query.fetchcols(cols):
+            self.total += 1
+            if completed_date is None:
+                self.active += 1
+                if due_date < now:
+                    self.overdue += 1
+                if locked_by_id is not None:
+                    self.locked += 1
+            else:
+                self.completed += 1
+
+    def __iter__(self):
+        self.refresh()
+        return iter([
+            ('Total', self.total),
+            ('Active', self.active),
+            ('Overdue', self.overdue),
+            ('Completed', self.completed),
+            ('Locked', self.locked),
+        ])
+
+
+def delete_queue(queue_id):
+    assert queue_id is not None
+    query = globals.db.query('workqueues')
+    query.where('queue_id = %s', queue_id)
+    queue = query.fetchone()
+    query = globals.db.query('tasks')
+    query.where('queue_id = %s', queue_id)
+    query.where('completed_date is not null')
+    query.delete()
+    try:
+        queue.db_delete()
+    except dbobj.ConstraintError:
+        raise TaskError('workqueue %r cannot be deleted as it has outstanding tasks' % queue.name)
+
+
+class AssignHelper:
+    assign_types = [
+        ('me', 'Me'),
+        ('myunit', 'My ' + config.unit_label.lower()),
+        ('originator', 'Originator'),
+        ('assigner', 'Last assigner'),
+        ('queue', 'Task queue'),
+        ('user', 'Another user'),
+        ('unit', 'Another ' + config.unit_label.lower()),
+    ]
+    workqueues = workqueues
+
+    def __init__(self, db, queue_id, 
+                 this_unit_id, this_user_id, originator_id, last_assigner_id):
+        self.queue_id = None
+        self.this_unit_id = this_unit_id
+        self.this_user_id = this_user_id
+        self.originator_id = originator_id
+        self.last_assigner_id = last_assigner_id
+        self.unit_id = None
+        self.user_id = None
+        self.assign_type = 'me'
+        if queue_id is not None:
+            query = db.query('workqueues')
+            query.where('queue_id = %s', queue_id)
+            queue = query.fetchone()
+            if queue.unit_id is not None:
+                if queue.unit_id == self.this_unit_id:
+                    self.assign_type = 'myunit'
+                else:
+                    self.assign_type = 'unit'
+                    self.unit_id = queue.unit_id
+            elif queue.user_id is not None:
+                if queue.user_id == self.this_user_id:
+                    self.assign_type = 'me'
+                elif queue.user_id == self.originator_id:
+                    self.assign_type = 'originator'
+                else:
+                    self.assign_type = 'user'
+                    self.user_id = queue.user_id
+            else:
+                self.assign_type = 'queue'
+                self.queue_id = queue_id
+        disable = set()
+        if self.originator_id == this_user_id:
+            disable.add('originator')
+        if self.last_assigner_id == this_user_id:
+            disable.add('assigner')
+        if self.last_assigner_id == self.originator_id:
+            disable.add('assigner')
+        if not self.workqueues:
+            disable.add('queue')
+        self.assign_types = [(k, v) for k, v in self.assign_types
+                                if k not in disable]
+
+    def get_queue_id(self, db):
+        if self.assign_type == 'queue':
+            try:
+                return int(self.queue_id)
+            except TypeError:
+                # This might happen if no workqueues are configured.
+                raise TaskError('Task is assigned to an invalid queue')
+        if self.assign_type in ('myunit', 'unit'):
+            attr = 'unit_id'
+        else:
+            attr = 'user_id'
+        if self.assign_type == 'me':
+            value = self.this_user_id
+        elif self.assign_type == 'myunit':
+            value = self.this_unit_id
+        elif self.assign_type == 'originator':
+            value = self.originator_id
+        elif self.assign_type == 'assigner':
+            value = self.last_assigner_id
+        else:
+            value = getattr(self, attr)
+        while 1:
+            query = db.query('workqueues')
+            query.where('%s = %%s' % attr, value)
+            queue = query.fetchone()
+            if queue is not None:
+                return queue.queue_id
+            queue = db.new_row('workqueues')
+            setattr(queue, attr, value)
+            try:
+                queue.db_update()
+            except dbobj.DuplicateKeyError:
+                pass
+            else:
+                return queue.queue_id
+
+    def unit_str(self):
+        if self.unit_id is None:
+            return ''
+        return unituser.units[self.unit_id].name
+
+    def user_str(self):
+        if self.user_id is None:
+            return ''
+        return unituser.users[self.user_id].username
+
+    def originator_str(self):
+        if self.originator_id is None:
+            return ''
+        return unituser.users[self.originator_id].username
+
+    def last_assigner_str(self):
+        if self.last_assigner_id is None:
+            return ''
+        return unituser.users[self.last_assigner_id].username
+
+
+
+class _TaskBase:
+    _copy_attrs = (
+        'queue_id', 'action', 'task_description', 'annotation',
+        'case_id', 'form_name', 'summary_id', 
+        'originator_id', 'assigner_id', 'active_date', 'due_date',
+    )
+    def _fetch(self, db, task_id):
+        return db.query('tasks').where('task_id = %s', task_id).fetchone()
+
+    def _locked_fetch(self, db, task_id, user_id=None):
+        query = db.query('tasks', for_update=True)
+        query.where('task_id = %s', task_id)
+        if user_id is not None:
+            query.where('locked_by_id = %s', user_id)
+        return query.fetchone()
+
+    def _copy(self, src, dst):
+        for attr in self._copy_attrs:
+            setattr(dst, attr, getattr(src, attr))
+
+    def _init(self, user_id):
+        self.queue_id = None
+        self.case_id = None
+        self.task_description = ''
+        self.annotation = ''
+        self.form_name = None
+        self.summary_id = None
+        self.originator_id = user_id
+        self.assigner_id = user_id
+        self.active_date = None
+        self.due_date = None
+
+
+def _our_lock(task, user_id):
+    return task.locked_by_id is not None and task.locked_by_id == user_id
+
+def _set_unlocked(task):
+    task.locked_by_id = None
+    task.locked_date = None
+
+def _set_completed(task, user_id):
+    assert user_id is not None
+    if not task.completed_date:
+        task.completed_date = datetime.now()
+        task.completed_by_id = user_id
+
+def _set_assigned(task, user_id):
+    assert user_id is not None
+    task.assigner_id = user_id
+    task.assignment_date = datetime.now()
+
+def _clear_completed(task):
+    task.completed_date = None
+    task.completed_by_id = None
+
+class EditTask(_TaskBase):
+    active_options = [
+        ('now', 'Immediately'),
+        ('tomorrow', 'Tomorrow'),
+        ('monday', 'Monday'),
+        ('tuesday', 'Tuesday'),
+        ('wednesday', 'Wednesday'),
+        ('thursday', 'Thursday'),
+        ('friday', 'Friday'),
+        ('saturday', 'Saturday'),
+        ('sunday', 'Sunday'),
+        ('week', 'One week'),
+        ('fortnight', 'Two weeks'),
+        ('month', 'One month'),
+        ('quarter', 'Three months'),
+    ]
+    due_options = [
+        ('', 'No deadline'),
+        ('now', 'As soon as possible'),
+        ('1h', 'One hour'),
+        ('4h', 'Four hours'),
+        ('tomorrow', 'One day'),
+        ('monday', 'Monday after active'),
+        ('tuesday', 'Tuesday after active'),
+        ('wednesday', 'Wednesday after active'),
+        ('thursday', 'Thursday after active'),
+        ('friday', 'Friday after active'),
+        ('saturday', 'Saturday after active'),
+        ('sunday', 'Sunday after active'),
+        ('week', 'One week after active'),
+        ('fortnight', 'Two weeks after active'),
+        ('month', 'One month after active'),
+        ('quarter', 'Three months after active'),
+    ]
+    repeat_options = [
+        ('none', 'Don\'t repeat'),
+        ('hourly', 'Hourly'),
+        ('twohourly', 'Every 2 Hours'),
+        ('fourhourly', 'Every 4 Hours'),
+        ('daily', 'Daily'),
+        ('weekly', 'Weekly'),
+        ('monthly', 'Monthly'),
+    ]
+    repeat_delta_map = {
+        'hourly': DateTime.RelativeDateTime(hours=1),
+        'twohourly': DateTime.RelativeDateTime(hours=2),
+        'fourhourly': DateTime.RelativeDateTime(hours=4),
+        'daily': DateTime.RelativeDateTime(days=1),
+        'weekly': DateTime.RelativeDateTime(weeks=1),
+        'monthly': DateTime.RelativeDateTime(months=1),
+    }
+    repeatcount_options = ['n/a'] + range(2, 53)
+
+    def __init__(self, db, credentials, task_row=None, 
+                 case_id=None, inplace=False):
+        self.credentials = credentials
+        self.inplace = inplace
+        self.this_user_id = self.credentials.user.user_id
+        self.repeat = self.repeat_options[0][0]
+        self.repeatcount = self.repeatcount_options[0][0]
+        self.datetime_format = datetime.mx_parse_datetime.format
+        if task_row is None:
+            self._init(self.this_user_id)
+            self.seed_task_id = None
+            if case_id is not None:
+                self.case_id = case_id
+        else:
+            self._copy(task_row, self)
+            self.seed_task_id = task_row.task_id
+            if self.inplace:
+                self.active_options = [('nochange', 'No change')] \
+                            + self.active_options
+                self.due_options = [('nochange', 'No change')] \
+                            + self.due_options
+        self.set_queue_id(db, self.queue_id)
+        self.active = self.active_options[0][0]
+        self.due = self.due_options[0][0]
+        self.active_abs = self.due_abs = ''
+        if self.active_date:
+            self.active_abs = str(self.active_date)
+        if self.due_date:
+            self.due_abs = str(self.due_date)
+
+    def set_queue_id(self, db, queue_id):
+        self.assignee = AssignHelper(db, queue_id,
+                                     self.credentials.unit.unit_id,
+                                     self.credentials.user.user_id,
+                                     self.originator_id, self.assigner_id)
+
+    def set_params(self, db, queue_id=None, active_date=None, due_date=None, 
+                   task_description=None, annotation=None, form_name=None):
+        if queue_id is not None:
+            self.set_queue_id(db, queue_id)
+        if active_date is not None:
+            self.active = datetime.to_discrete(active_date) 
+        if due_date is not None:
+            self.due = datetime.to_discrete(due_date) 
+        if task_description is not None:
+            self.task_description = task_description
+        if annotation is not None:
+            self.annotation = annotation
+        if form_name is not None:
+            self.form_name = form_name
+
+    def _update(self, db, inplace=False, complete=False):
+        if inplace:
+            # Update in place
+            task = self._locked_fetch(db, self.seed_task_id, self.this_user_id)
+            if task is None:
+                raise TaskError('Update failed - the task has been changed'
+                                ' by another user')
+            _set_unlocked(task)
+            _clear_completed(task)
+        else:
+            # Create a new task, closing the old one if necessary.
+            if self.seed_task_id is not None:
+                task = self._locked_fetch(db, self.seed_task_id)
+                if _our_lock(task, self.this_user_id):
+                    _set_unlocked(task)
+                _set_completed(task, self.this_user_id)
+                task.db_update()
+            task = db.new_row('tasks')
+            task.parent_task_id = self.seed_task_id
+            task.creation_date = datetime.now()
+        self._copy(self, task)
+        if not self.task_description or not self.task_description.strip():
+            raise TaskError('Task must have a description')
+        old_due = None
+        if task.due_date and task.active_date:
+            old_due = task.due_date - task.active_date
+        if self.active_abs and self.active_abs.strip():
+            try:
+                active_abs = datetime.mx_parse_datetime(self.active_abs)
+            except datetime.Error, e:
+                raise TaskError('Start date: %s' % e)
+            if not datetime.near(active_abs, task.active_date):
+                task.active_date = active_abs
+        elif self.active != 'nochange':
+            task.active_date = datetime.parse_discrete(self.active)
+        if self.due_abs and self.due_abs.strip():
+            try:
+                due_abs = datetime.mx_parse_datetime(self.due_abs)
+            except datetime.Error, e:
+                raise TaskError('Complete by date: %s' % e)
+            if not datetime.near(due_abs, task.due_date):
+                task.due_date = due_abs
+        elif self.due != 'nochange':
+            task.due_date = datetime.parse_discrete(self.due, task.active_date)
+        if self.active != 'nochange' and self.due == 'nochange' and old_due:
+            # If active date has changed, but due date has not, preserve the
+            # relationship between the old active date and the old due date...
+            task.due_date = task.active_date + old_due
+        _set_assigned(task, self.this_user_id)
+        task.queue_id = self.assignee.get_queue_id(db)
+        if complete:
+            _set_completed(task, self.this_user_id)
+        assert task.assigner_id is not None
+        task.db_update()
+        if not complete and self.repeat != 'none':
+            try:
+                repeatcount = int(self.repeatcount)
+            except ValueError:
+                raise TaskError('invalid repeat count')
+            repeat_delta = self.repeat_delta_map[self.repeat]
+            for n in xrange(1, repeatcount):
+                task = task.db_clone()
+                task.active_date += repeat_delta
+                if task.due_date:
+                    task.due_date += repeat_delta
+                task.db_update()
+        globals.notify.notify('taskdesc')
+
+    def update(self, db):
+        # Fix up types from web interaction
+        if self.form_name == 'None':
+            self.form_name = None
+        if self.summary_id == 'None' or not self.form_name:
+            self.summary_id = None
+        self.action = guess_action(self)
+        self._update(db, inplace=self.inplace)
+
+    def close(self, db):
+        self.form_name = self.summary_id = None
+        self.action = ACTION_THREAD_COMPLETED
+        self._update(db, complete=True, inplace=False)
+
+    def delete(self, db):
+        self.form_name = self.summary_id = None
+        self.action = ACTION_THREAD_DELETED
+        self._update(db, complete=True, inplace=True)
+
+
+class UnlockedTask(_TaskBase):
+    def __init__(self, db, credentials, task_id):
+        self.task_id = task_id
+        task = self._fetch(db, self.task_id)
+        self._copy(task, self)
+
+class LockedTask(_TaskBase):
+    def __init__(self, db, credentials, task_id):
+        self.user_id = credentials.user.user_id
+        self.task_id = task_id
+        self.done = False
+        task = self._locked_fetch(db, self.task_id)
+        self._copy(task, self)
+        self.assigner = unituser.users[task.assigner_id]
+        self.was_locked = None
+        if task.locked_by_id is not None and task.locked_by_id != self.user_id:
+            username = unituser.users[task.locked_by_id].username
+            self.was_locked = '%s %s' % (username,
+                                         datetime.relative(task.locked_date))
+        task.locked_by_id = self.user_id
+        task.locked_date = datetime.now()
+        task.db_update()
+
+    def unlock(self, db):
+        task = self._locked_fetch(db, self.task_id, self.user_id)
+        if task is None:
+            return
+        _set_unlocked(task)
+        task.db_update()
+
+    def same_entity(self, action, case_id, form_name=None, summary_id=None):
+        return same_entity(self, action, case_id, form_name, summary_id)
+
+    def entity_update(self, db, action, case_id,
+                      form_name=None, summary_id=None):
+        """
+        When a case or form is updated, check if it is the subject
+        of this task and note the fact by setting the "done" flag.
+        
+        If the case or form is new, update the task record with the
+        record's primary key (turning a "create" request into an "edit"),
+        so a subsequent edit will return to the newly created record,
+        whether the task is is completed or not.
+        """
+        task = self._locked_fetch(db, self.task_id, self.user_id)
+        if task is None:
+            return
+        if not same_entity(task, action, case_id, form_name, summary_id):
+            return
+        self.done = True
+        task.case_id = self.case_id = case_id
+        task.form_name = self.form_name = form_name
+        task.summary_id = self.summary_id = summary_id
+        task.db_update()
diff --git a/casemgr/tasksearch.py b/casemgr/tasksearch.py
new file mode 100644
index 0000000..cd8e930
--- /dev/null
+++ b/casemgr/tasksearch.py
@@ -0,0 +1,299 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import copy
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import datetime, trafficlight
+from casemgr import paged_search, demogfields, globals, tasks, cached
+import config
+
+def case_summary(db, case_id, case_dict, person_dict):
+    case_row = case_dict.get(case_id)
+    if case_row is None:
+        return 'unknown'
+    fields = demogfields.get_demog_fields(db, case_row.syndrome_id)
+    fields = fields.context_fields('result')
+    person_row = person_dict.get(case_row.person_id)
+    if person_row is not None:
+        summary = '%s, %s' % (fields.summary(case_row), 
+                              fields.summary(person_row))
+    return summary
+
+
+def task_subscription_query(query, credentials):
+    subquery = query.in_select('queue_id', 'workqueue_members',
+                                conjunction='OR')
+    subquery.where('unit_id = %s', credentials.unit.unit_id)
+    subquery.where('user_id = %s', credentials.user.user_id)
+    subquery = subquery.union_query('workqueues', conjunction='OR')
+    subquery.where('unit_id = %s', credentials.unit.unit_id)
+    subquery.where('user_id = %s', credentials.user.user_id)
+
+
+class TaskSearchParams:
+    def __init__(self, view_type, order_by):
+        self.view_type = view_type
+        self.order_by = order_by
+        self.include_future = False
+        self.include_completed = False
+        self.include_deleted_cases = False
+
+    def __eq__(self, other):
+        if not isinstance(other, TaskSearchParams):
+            return False
+        return (self.view_type == other.view_type and
+                self.order_by == other.order_by and
+                self.include_future == other.include_future and
+                self.include_completed == other.include_completed and
+                self.include_deleted_cases == other.include_deleted_cases)
+
+    def __ne__(self, other):
+        return not self == other
+
+    def copy(self):
+        return copy.copy(self)
+
+    def __repr__(self):
+        return 'TaskSearchParams(%r, %r, %r, %r, %r)' %\
+            (self.view_type, self.order_by, self.include_future,
+             self.include_completed, self.include_deleted_cases)
+            
+
+class TaskSearch(paged_search.SortablePagedSearch):
+    orders = [
+        ('due_date,active_date', 'Due Date'),
+        ('active_date', 'Active Date'),
+        ('completed_date', 'Completed Date'),
+        ('task_description,task_id', 'Task Description'),
+        ('users.username', 'Assigner'),
+        ('task_id', 'Task ID'),
+        ('case_id,task_id', 'Case ID'),
+    ]
+    views = [
+        ('active', 'Active tasks'),
+        ('assignee', 'Edit my tasks'),
+        ('overdue', 'Overdue tasks'),
+    ]
+
+    def __init__(self, db, credentials, prefs, case_id=None):
+        self.db = db
+        self.credentials = credentials
+        self.case_id = case_id
+        self.only_overdue = False
+        if 'ACCESSALL' in self.credentials.rights:
+            self.views = self.views + [('alloverdue', 'All overdue tasks')]
+        saved_params = prefs.get('ts_params')
+        if saved_params is None:
+            self.params = TaskSearchParams(view_type=self.views[0][0],
+                                           order_by=self.orders[0][0])
+        else:
+            self.params = saved_params.copy()
+        self.last_params = None
+        paged_search.SortablePagedSearch.__init__(self, db, prefs)
+
+    def user_views(self, credentials):
+        views = list(self.views)
+        for queue in tasks.user_workqueues(credentials):
+            name = queue.name
+            if len(name) > 20:
+                name = name[:20] + '...'
+            views.append(('queue_%d' % queue.queue_id, name + ' queue'))
+        return views
+
+    def new_search(self):
+        if self.params == self.last_params:
+            return
+        paged_search.SortablePagedSearch.new_search(self)
+        is_admin = 'ACCESSALL' in self.credentials.rights
+        query = self.db.query('tasks', order_by=self.params.order_by)
+        self.viewonly = 'VIEWONLY' in self.credentials.rights
+        self.allow_edit = False
+        if self.case_id is not None:
+            query.where('case_id = %s', self.case_id)
+        if not self.params.include_deleted_cases and self.case_id is None:
+            query.join('LEFT JOIN cases USING (case_id)')
+            subquery = query.sub_expr(conjunction='OR')
+            subquery.where('NOT cases.deleted')
+            subquery.where('cases.deleted IS null')
+            subquery.where('tasks.case_id IS null')
+        if self.params.view_type == 'assignee':
+            self.allow_edit = True
+            subquery = query.sub_expr(conjunction = 'OR')
+            subquery.where('assigner_id = %s', self.credentials.user.user_id)
+            subquery.where('originator_id = %s', self.credentials.user.user_id)
+        elif self.params.view_type == 'alloverdue' and is_admin:
+            # admin sees everything
+            self.allow_edit = True
+        elif self.params.view_type.startswith('queue_'):
+            queue_id = int(self.params.view_type[len('queue_'):])
+            query.where('queue_id = %s', queue_id)
+        else:
+            subquery = query.sub_expr(conjunction = 'OR')
+            # Filter for tasks assigned to us
+            task_subscription_query(subquery, self.credentials)
+            # OR tasks assigned by us that are overdue
+            overdue_query = subquery.sub_expr(conjunction = 'AND')
+            uquery = overdue_query.sub_expr(conjunction = 'OR')
+            uquery.where('assigner_id = %s', self.credentials.user.user_id)
+            uquery.where('originator_id = %s', self.credentials.user.user_id)
+            overdue_query.where('due_date <= CURRENT_TIMESTAMP')
+        if self.params.order_by.startswith('users.'):
+            query.join('JOIN users ON (users.user_id = assigner_id)')
+        if (not bool(self.params.include_future) 
+            and self.params.view_type != 'assignee'):
+            query.where('active_date <= CURRENT_TIMESTAMP')
+        if not bool(self.params.include_completed):
+            query.where('completed_date is null')
+        if self.params.view_type in ('overdue', 'alloverdue'):
+            query.where('due_date <= CURRENT_TIMESTAMP')
+        self.query = query
+        self.last_params = self.params.copy()
+        self.prefs.set('ts_params', self.last_params)
+
+    def page_rows(self, cred):
+        self.page_jump()
+        # Fetch task rows
+        results = paged_search.SortablePagedSearch.page_rows(self)
+        # Fetch associated entities
+        case_dict = self.db.table_dict('cases')
+        user_dict = self.db.table_dict('users')
+        unit_dict = self.db.table_dict('units')
+        person_dict = self.db.table_dict('persons')
+        queue_dict = self.db.table_dict('workqueues')
+        for task in results:
+            if task.case_id is not None:
+                case_dict.want(task.case_id)
+            if task.originator_id is not None:
+                user_dict.want(task.originator_id)
+            if task.assigner_id is not None:
+                user_dict.want(task.assigner_id)
+            if task.locked_by_id is not None:
+                user_dict.want(task.locked_by_id)
+            if task.completed_by_id is not None:
+                user_dict.want(task.completed_by_id)
+            queue_dict.want(task.queue_id)
+        queue_dict.preload()
+        for queue in queue_dict.values():
+            if queue.unit_id is not None:
+                unit_dict.want(queue.unit_id)
+            if queue.user_id is not None:
+                user_dict.want(queue.user_id)
+        case_dict.preload()
+        unit_dict.preload()
+        user_dict.preload()
+        for case in case_dict.itervalues():
+            person_dict.want(case.person_id)
+        person_dict.preload()
+        # Now join it all together
+        now = datetime.now()
+        for task in results:
+            task.active_relative = datetime.relative(task.active_date, now)
+            active_days = (now - task.active_date).days
+            task.active_color = trafficlight.web_trafficlight(active_days, 30)
+            if task.completed_date:
+                task.complete_color = '#cccccc'
+                if task.action == tasks.ACTION_THREAD_DELETED:
+                    task.complete_relative = 'DELETED'
+                else:
+                    task.complete_relative = 'completed'
+                completed_by = user_dict.get(task.completed_by_id)
+                if completed_by is not None:
+                    task.complete_relative += ' by %s ' % completed_by.username
+                task.complete_relative += str(task.completed_date)
+            elif task.due_date is None:
+                task.complete_color = 'transparent'
+                task.complete_relative = ''
+            else:
+                due_days = (now - task.due_date).days + 15
+                task.complete_color = trafficlight.web_trafficlight(due_days, 30)
+                task.complete_relative = datetime.relative(task.due_date, now)
+            task.originator = user_dict.get(task.originator_id)
+            task.assigner = user_dict.get(task.assigner_id)
+            task.locked_by = user_dict.get(task.locked_by_id)
+            task.locked_relative = datetime.relative(task.locked_date, now)
+            queue = queue_dict.get(task.queue_id)
+            if queue is None:
+                task.assignee = ''
+            elif queue.unit_id is not None:
+                task.assignee = '%s: %s' %\
+                    (config.unit_label, unit_dict.get(queue.unit_id).name)
+            elif queue.user_id is not None:
+                task.assignee = 'User: %s' %\
+                    user_dict.get(queue.user_id).username
+            else:
+                task.assignee = 'Queue: %s' % queue.name
+            task.case_summary = task.contact_summary = ''
+            if task.case_id is not None:
+                task.case_summary = case_summary(self.db, task.case_id,
+                                                 case_dict, person_dict)
+            task.action_summary = tasks.action_desc.get(task.action, 'unknown')
+        return results
+
+
+class QuickTask:
+
+    cols = (
+        'tasks.task_id', 'cases.case_id',
+        'persons.surname', 'persons.given_names', 
+        'tasks.task_description', 'tasks.due_date', 'tasks.active_date',
+    )
+
+    def __init__(self, now, task_id, case_id, surname, given_names, 
+                 description, due_date, active_date):
+        self.task_id = task_id
+        self.case_id = case_id
+        self.surname = surname
+        self.given_names = given_names
+        self.description = description
+        self.due_relative = datetime.relative(due_date or active_date, now)
+
+
+class QuickTasks(list, cached.Cached):
+
+    def __init__(self, credentials):
+        self.credentials = credentials
+
+    def load(self):
+        query = globals.db.query('tasks', order_by='due_date,active_date',                                       limit=10)
+        # Exclude deleted cases via /cases/ LEFT JOIN
+        query.join('LEFT JOIN cases USING (case_id)')
+        subquery = query.sub_expr(conjunction='OR')
+        subquery.where('NOT cases.deleted')
+        subquery.where('cases.deleted IS null')
+        subquery.where('tasks.case_id IS null')
+        if 0:
+            # Filter for tasks assigned to us
+            subquery = query.sub_expr(conjunction = 'OR')
+            task_subscription_query(subquery, self.credentials)
+            # OR tasks assigned by us that are overdue
+            overdue_query = subquery.sub_expr(conjunction = 'AND')
+            uquery = overdue_query.sub_expr(conjunction = 'OR')
+            uquery.where('assigner_id = %s', self.credentials.user.user_id)
+            uquery.where('originator_id = %s', self.credentials.user.user_id)
+            overdue_query.where('due_date <= CURRENT_TIMESTAMP')
+        else:
+            task_subscription_query(query, self.credentials)
+        query.where('active_date <= CURRENT_TIMESTAMP')
+        query.where('completed_date is null')
+        query.join('LEFT JOIN persons USING (person_id)')
+        now = datetime.now()
+        self[:] = [QuickTask(now, *row) 
+                   for row in query.fetchcols(QuickTask.cols)]
diff --git a/casemgr/unituser.py b/casemgr/unituser.py
new file mode 100644
index 0000000..8708271
--- /dev/null
+++ b/casemgr/unituser.py
@@ -0,0 +1,154 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Read-only cache of units and users
+
+Note that we defer importing "globals" until it's needed, as parts of this code
+are used by the initial installer, and globals in not valid in that context.
+
+"""
+
+from casemgr.rights import Rights
+
+NULL_UNIT_ID = NULL_USER_ID = NULL_ID = -1
+
+class DummyUnit:
+    name = '---'
+    unit_id = NULL_UNIT_ID
+    rights = Rights()
+
+
+class DummyUser:
+    user_id = NULL_USER_ID
+    username = '---'
+    fullname = '---'
+    rights = Rights()
+
+
+class CacheUser(object):
+    def apply(self, dbrow):
+        self.user_id = dbrow.user_id
+        self.username = dbrow.username
+        self.fullname = dbrow.fullname
+        self.rights = Rights(dbrow.rights)
+
+    def __nonzero__(self):
+        return self.user_id != NULL_USER_ID
+
+
+class CacheUnit(object):
+    def apply(self, dbrow):
+        self.unit_id = dbrow.unit_id
+        self.name = dbrow.name
+        self.unit_rights = dbrow.rights
+        self._rights = None
+        self._groups = None
+
+    def __nonzero__(self):
+        return self.unit_id != NULL_UNIT_ID
+
+    def _load_group_rights(self):
+        from globals import db
+        rights = Rights(self.unit_rights)
+        groups = []
+        query = db.query('groups')
+        query.join('LEFT JOIN unit_groups USING (group_id)')
+        query.where('unit_groups.unit_id = %s', self.unit_id)
+        for group, grights in query.fetchcols(('group_name', 'rights')):
+            groups.append(group)
+            rights.add(grights)
+        self._groups = groups
+        self._rights = rights
+
+    def _get_groups(self):
+        if self._groups is None:
+            self._load_group_rights()
+        return self._groups
+    groups = property(_get_groups)
+
+    def _get_rights(self):
+        if self._rights is None:
+            self._load_group_rights()
+        return self._rights
+    rights = property(_get_rights)
+
+
+class Refresher:
+    def __init__(self):
+        self.by_id = {}
+        self.subscribed = False
+
+    def notification(self, *args):
+        try:
+            ids = map(int, args)
+        except ValueError:
+            return
+        self.load(*ids)
+
+    def add(self, row):
+        id = getattr(row, self.id_col)
+        if id != NULL_ID and not self.subscribed:
+            from globals import notify
+            self.subscribed = True
+            if not notify.subscribe(self.entity_name, self.notification):
+                # XXX Notification not available - use time based refresh?
+                pass
+        try:
+            inst = self.by_id[id]
+        except KeyError:
+            inst = self.by_id[id] = self.inst_class()
+        inst.apply(row)
+        return inst
+
+    def load(self, *ids):
+        from globals import db
+        query = db.query(self.entity_name)
+        query.where_in(self.id_col, ids)
+        for row in query.fetchall():
+            self.add(row)
+
+    def fetch(self, *ids):
+        missing = [id for id in ids if id not in self.by_id]
+        self.load(*missing)
+        return [self.by_id[id] for id in ids]
+
+    def __getitem__(self, id):
+        try:
+            return self.by_id[id]
+        except KeyError:
+            self.load(id)
+            return self.by_id[id]
+
+
+class UserCache(Refresher):
+    entity_name = 'users'
+    id_col = 'user_id'
+    inst_class = CacheUser
+
+users = UserCache()
+null_user = users.add(DummyUser())
+
+
+class UnitCache(Refresher):
+    entity_name = 'units'
+    id_col = 'unit_id'
+    inst_class = CacheUnit
+
+units = UnitCache()
+null_unit = units.add(DummyUnit())
diff --git a/casemgr/user_edit.py b/casemgr/user_edit.py
new file mode 100644
index 0000000..fbd4547
--- /dev/null
+++ b/casemgr/user_edit.py
@@ -0,0 +1,377 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Logic to support editing users
+
+Includes:
+    SelfRegister        A new user pre-registering
+    DisabledEdit        A disabled user attempting to log in (edit details only)
+    Sponsor             Sponsor another user onto the system
+    SponsoredRegister   A new user registering via a sponsor key
+    SponsorEnable       Sponsor enabling a sponsored user
+    EditSelf            A logged-in user editing their own details
+    SystemAdmin         An admin adding or editing a user
+    RoleAdmin           A role-only admin adding or editing a user
+"""
+
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from mx import DateTime
+
+from cocklebur import dbobj, pageops, utils
+
+from casemgr import globals, credentials, messages, notify, unituser
+
+
+class ConfirmUserEnable(pageops.Confirm):
+    mode = 'enable'
+    title = 'You are enabling this account'
+    message = 'Have you verified the bona fides of this user?'
+    buttons = [
+        ('continue', 'No, I have not'),
+        ('confirm', 'Yes, I have'),
+    ]
+
+    def button_confirm(self, pageops, ctx):
+        ctx.locals.ue.confirm_enable()
+        self.resume(pageops, ctx)
+
+
+class UserEditBase:
+
+    admin_edit = False
+    edit_username = False
+    passwd_setting = False
+    passwd_setting_check_old = False
+    unregistered = False
+    submit_button = 'Save'
+    need_key = False
+    enabling = False
+    view_only = False
+
+    def __init__(self, cred, *load_args):
+        self.cred = cred
+        self.load(*load_args)
+        self.sponsor = None
+        if self.user.sponsoring_user_id is not None:
+            self.sponsor = unituser.users[self.user.sponsoring_user_id]
+        if self.passwd_setting:
+            self.pwd = credentials.NewPass()
+
+    def load(self, user_id=None):
+        if user_id is not None:
+            query = globals.db.query('users')
+            query.where('user_id = %s', user_id)
+            self.user = query.fetchone()
+            if self.user is None:
+                raise credentials.CredentialError('User not found')
+        else:
+            self.user = globals.db.new_row('users')
+            self.user.enabled = False
+
+    def lock_remain(self):
+        return credentials.timelock_remain_str(self.user)
+
+    def check_details(self):
+        return credentials.need_check(self.user)
+
+    def mark_checked(self):
+        credentials.mark_checked(self.user)
+        globals.db.commit()
+
+    def has_changed(self):
+        return self.user.db_has_changed()
+
+    def log(self, desc):
+        credentials.user_log(globals.db, self.user.user_id, desc)
+
+    def add_unit(self, unit_id):
+        uu = globals.db.new_row('unit_users')
+        uu.unit_id = unit_id
+        uu.user_id = self.user.user_id
+        uu.db_update()
+
+    def validate(self):
+        # Check username, fullname and contact details are okay
+        self.messages = messages.Messages()
+        if self.edit_username:
+            try:
+                credentials.valid_username(self.user)
+            except credentials.CredentialError, e:
+                self.messages.msg('err', e)
+        try:
+            credentials.valid_fullname(self.user)
+        except credentials.CredentialError, e:
+            self.messages.msg('err', e)
+        try:
+            credentials.valid_contact(self.user)
+        except credentials.CredentialError, e:
+            self.messages.msg('err', e)
+        # Check password
+        if self.passwd_setting and self.pwd.has_new():
+            try:
+                if self.passwd_setting_check_old:
+                    credentials.pwd_check(self.user, self.pwd.old)
+                self.pwd.set(self.user)
+            except credentials.CredentialError, e:
+                self.messages.msg('err', e)
+        if not self.user.password and self.user.enabled:
+            self.user.enabled = False
+            self.messages.msg('err', 'Set a password before enabling this user')
+
+    def save(self):
+        self.validate()
+        if self.messages.have_errors():
+            raise self.messages
+        if self.user.username:
+            self.user.username = self.user.username.lower()
+        if not self.admin_edit:
+            self.user.checked_timestamp = DateTime.now()
+        desc = self.user.db_desc()
+        try:
+            self.user.db_update()
+        except dbobj.DuplicateKeyError, e:
+            raise credentials.UsernameError('Sorry, that user name is already used - pick another')
+        self.log(desc)
+        self.done()
+
+    def done(self):
+        self.messages.msg('info', 'User details updated')
+
+
+class SelfRegister(UserEditBase):
+    
+    title = 'Register for an account'
+    unregistered = True
+    edit_username = True
+    submit_button = 'Apply'
+    passwd_setting = True
+    passwd_setting_check_old = False
+    passwd_setting_prompt = '''\
+        You must supply a strong password (please see the notes to the right):
+    '''
+
+    def log(self, desc):
+        credentials.user_log(globals.db, self.user.user_id, 'REGISTER')
+
+    def done(self):
+        notify.register_notify(self.user)
+        self.messages.msg('info', 
+            'Your account registration details have been recorded. An '
+            'administrator will contact you in order to verify the information '
+            'which you have provided.')
+
+
+class DisabledEdit(UserEditBase):
+
+    title = 'Account not activated. You can only review your details.'
+    unregistered = True
+    
+
+class Sponsor(UserEditBase):
+    
+    title = 'Enter details of the user you wish to sponsor'
+    edit_username = False
+
+    def __init__(self, cred, **user_attrs):
+        UserEditBase.__init__(self, cred)
+#        self.user.fullname = user_attrs['fullname']
+#        self.user.email = user_attrs['email']
+        for attr, value in user_attrs.iteritems():
+            setattr(self.user, attr, value)
+        self.save()
+
+    def key(self):
+        import binascii
+        return binascii.b2a_hex(utils.secret(128))
+
+    def save(self):
+        # canonical e-mail query
+        # if email already in user db, resend?
+        # what if already a user? what about generic addrs? Might want multiple
+        # outstanding invites?
+        #self.validate()
+        #if self.messages.have_errors():
+        #    raise self.messages
+        #query = globals.db.query('users')
+        #query.where('email = %s', self.user.email)
+        #row = query.fetchone()
+        #if row is not None:
+        #    if row.enabled:
+        #        raise credentials.CredentialsError('
+        self.user.sponsoring_user_id = self.cred.user.user_id
+        self.user.enable_key = self.key()
+        UserEditBase.save(self)
+        self.add_unit(self.cred.unit.unit_id)
+
+    def done(self):
+        self.messages.msg('info', 'An invitation has been sent to %r' % 
+                                self.user.email)
+#        self.messages.msg('info', 'Key is %s' % self.user.enable_key)
+
+
+class SponsoredRegister(UserEditBase):
+
+    title = 'Register for an account'
+    unregistered = True
+    edit_username = True
+    submit_button = 'Apply'
+    need_key = True
+    enable_key = None
+    passwd_setting = True
+    passwd_setting_check_old = False
+    passwd_setting_prompt = '''\
+        You must supply a strong password (please see the notes to the right):
+    '''
+
+    def __init__(self, cred, enable_key=None):
+        self.enable_key = enable_key
+        UserEditBase.__init__(self, cred)
+
+    def load(self):
+        if self.enable_key:
+            query = globals.db.query('users')
+            query.where('enable_key = %s', self.enable_key.strip())
+            user = query.fetchone()
+            if user is None:
+                raise credentials.CredentialError('Invalid key')
+            self.user = user
+            self.need_key = False
+        else:
+            self.user = globals.db.new_row('users')
+            self.user.enabled = False
+
+    def save(self):
+        self.user.enable_key = None
+        UserEditBase.save(self)
+
+    def done(self):
+        notify.register_notify(self.user)
+        self.messages.msg('info', 
+            'Your account registration details have been recorded. An '
+            'administrator will contact you in order to verify the information '
+            'which you have provided.')
+
+
+class SponsorEnable(UserEditBase):
+
+    title = 'Review and verify bona fides of sponsored user'
+    enabling = True
+    view_only = True
+    confirmed = False
+    submit_button = 'Verified'
+
+    def confirm_enable(self):
+        self.confirmed = True
+
+    def validate(self):
+        UserEditBase.validate(self)
+        if not self.confirmed:
+            raise ConfirmUserEnable
+        self.user.enabled = True
+
+
+class EditSelf(UserEditBase):
+
+    title = 'Edit your account details'
+    passwd_setting = True
+    passwd_setting_check_old = True
+    passwd_setting_prompt = '''\
+        The following fields only need to be completed if you wish to
+        change your password (please read the password selection notes to
+        the right):
+    '''
+
+    def __init__(self, cred):
+        UserEditBase.__init__(self, cred, cred.user.user_id)
+
+    def done(self):
+        if not self.user.privacy:
+            self.messages.msg('warn', credentials.privacy_reminder)
+
+
+class AdminCommon(UserEditBase):
+
+    title = 'Add a new user'
+    edit_username = True
+    admin_edit = True
+    passwd_setting = True
+    passwd_setting_check_old = False
+    passwd_setting_prompt = '''\
+        The following fields only need to be completed if you wish to
+        change the user's password (please read the password selection
+        notes to the right):
+    '''
+
+    def __init__(self, cred, user_id):
+        if 'UNITADMIN' not in cred.rights and 'ADMIN' not in cred.rights:
+            raise credentials.CredentialError('You are not an administrator')
+        UserEditBase.__init__(self, cred, user_id)
+        self.was_enabled = self.user.enabled
+        self.rights = list(credentials.Rights(self.user.rights))
+        if not self.user.is_new():
+            self.title = 'Edit details for user %r - %s' % (
+                    self.user.username, self.user.fullname)
+
+    def reset_attempts(self):
+        credentials.reset_attempts(self.user)
+        globals.db.commit()
+
+    def has_changed(self):
+        self.user.rights = str(credentials.Rights(self.rights))
+        return UserEditBase.has_changed(self)
+
+    def log(self, desc):
+        UserEditBase.log(self, desc)
+        credentials.admin_log(globals.db, self.cred.user.user_id, desc)
+
+    def validate(self):
+        UserEditBase.validate(self)
+        if self.messages.have_errors():
+            raise self.messages
+        if self.user.enabled and not self.was_enabled:
+            raise ConfirmUserEnable
+        self.user.rights = str(credentials.Rights(self.rights))
+
+    def confirm_enable(self):
+        self.was_enabled = True
+
+
+class SystemAdmin(AdminCommon):
+
+    def delete(self):
+        credentials.delete_user(self.user)
+        globals.db.commit()
+
+    def undelete(self):
+        credentials.undelete_user(self.user)
+        globals.db.commit()
+
+
+class RoleAdmin(AdminCommon):
+    
+    def save(self):
+        is_new = self.user.is_new()
+        AdminCommon.save(self)
+        if is_new:
+            self.add_unit(self.cred.unit.unit_id)
diff --git a/casemgr/user_search.py b/casemgr/user_search.py
new file mode 100644
index 0000000..a77e19e
--- /dev/null
+++ b/casemgr/user_search.py
@@ -0,0 +1,201 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Users searching for other user's contact details
+"""
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+import config
+
+privopts = [
+    ('email', 'e-mail address'),
+    ('phone_home', 'Home Phone No.'),
+    ('phone_work', 'Work Phone No.'),
+    ('phone_mobile', 'Mobile Phone No.'),
+    ('phone_fax', 'Fax No.'),
+]
+privcols = set([o[0] for o in privopts])
+
+class Privacy(set):
+    def __init__(self, privacy=()):
+        if privacy is None or privacy == '!':
+            privacy = ()
+        if isinstance(privacy, basestring):
+            privacy = privacy.split(',')
+        set.__init__(self, privacy)
+
+
+class PrivacyEdit:
+    privopts = privopts
+
+    def __init__(self, user_id):
+        self.user_id = user_id
+        query = globals.db.query('users')
+        query.where('user_id = %s', self.user_id)
+        rows = query.fetchcols(('privacy',))
+        assert len(rows) == 1
+        self.privacy = rows[0][0]
+        if not self.privacy or self.privacy == '!':
+            self.share = 'False'
+            self.share_details = []
+        else:
+            self.share = 'True'
+            self.share_details = self.privacy.split(',')
+
+    def update(self):
+        if self.share == 'False':
+            privacy = '!'
+        else:
+            privacy = ','.join(self.share_details + ['fullname'])
+        if self.privacy != privacy:
+            curs = globals.db.cursor()
+            dbobj.execute(curs, 'UPDATE users SET privacy=%s WHERE user_id=%s',
+                          (privacy, self.user_id))
+            globals.db.commit()
+            self.privacy = privacy
+
+
+class UserSearchBase(paged_search.SortablePagedSearch):
+    order_by = 'fullname'
+    headers = [
+        ('fullname', 'Full Name'),
+        ('username', 'Username'),
+        (None, 'Title/Agency/Expertise'),
+        (None, config.unit_label),
+        ('email', 'e-mail'),
+        (None, 'Phone'),
+        (None, 'Fax No.'),
+    ]
+
+    def __init__(self, prefs, query=None):
+        paged_search.SortablePagedSearch.__init__(self, globals.db, prefs, 
+                                                  query, title='User search')
+
+    def row_format(self, row, users_units=None):
+        def _field(attr):
+            value = getattr(row, attr)
+            if value and (attr not in privcols or attr in privacy):
+                return value
+            return ''
+        def _subfields(*a):
+            fields = []
+            for attr, label in a:
+                value = getattr(row, attr)
+                if value and (attr not in privcols or attr in privacy):
+                    fields.append('%s: %s' % (label, value))
+            return fields
+
+        if users_units is None:
+            uu = ''
+        else:
+            uu = users_units[row.user_id].comma_list('name')
+        privacy = Privacy(row.privacy)
+        return [
+            row.fullname, 
+            row.username, 
+            _subfields(('title', 'Job Title'),
+                       ('agency', 'Agency/Employer'),
+                       ('expertise', 'Expertise')),
+            uu,
+            _field('email'),
+            _subfields(('phone_work', 'WK'),
+                       ('phone_home', 'HM'),
+                       ('phone_mobile', 'MOB')),
+            _field('phone_fax'),
+        ]
+
+    def load_units(self, users):
+        user_ids = [u.user_id for u in users]
+        users_units = globals.db.participation_table('unit_users',
+                                                     'user_id', 'unit_id')
+        users_units.preload(user_ids)
+        return users_units
+
+    def result_page(self):
+        page = []
+        rows = paged_search.SortablePagedSearch.result_page(self)
+        users_units = self.load_units(rows)
+        for row in rows:
+            page.append(self.row_format(row, users_units))
+        return page
+
+    def yield_rows(self):
+        cols = ['fullname', 'username', 'title', 'agency', 'expertise',
+                'unit', 'email', 'phone_work', 'phone_home', 'phone_mobile',
+                'phone_fax']
+        yield cols
+        chunk_size = 100
+        for i in range(0, len(self.pkeys), chunk_size):
+            chunk_keys = self.pkeys[i:i + chunk_size]
+            query = self.db.query(self.table)
+            rows = query.fetchall_by_keys(chunk_keys)
+            users_units = self.load_units(rows)
+            for row in rows:
+                line = []
+                for col in cols:
+                    value = ''
+                    if col not in privcols or col in Privacy(row.privacy):
+                        if col == 'unit':
+                            value = users_units[row.user_id].comma_list('name')
+                        else:
+                            value = getattr(row, col)
+                    line.append(value or '')
+                yield line
+
+
+class UserSearch(UserSearchBase):
+
+    def __init__(self, prefs, term=None):
+        self.new_query(term)
+        UserSearchBase.__init__(self, prefs)
+
+    def new_query(self, term=None):
+        self.reset()
+        self.term = term
+        self.query = globals.db.query('users', order_by=self.order_by)
+        #self.query.where('enabled')
+        self.query.where('not deleted')
+        self.query.where('privacy IS NOT NULL')
+        self.query.where("privacy != '!'")
+        if self.term:
+            if '*' in self.term:
+                term_like = dbobj.wild(self.term)
+            else:
+                term_like = '%%%s%%' % self.term
+            orquery = self.query.sub_expr('OR')
+            orquery.where('fullname ILIKE %s', term_like)
+            orquery.where('username ILIKE %s', term_like)
+            orquery.where('title ILIKE %s', term_like)
+            orquery.where('agency ILIKE %s', term_like)
+            orquery.where('expertise ILIKE %s', term_like)
+
+
+class UserByUnitSearch(UserSearchBase):
+    def __init__(self, prefs, unit_id):
+        query = globals.db.query('users', order_by='fullname')
+        query.join('JOIN unit_users USING (user_id)')
+        query.where('unit_id = %s', unit_id)
+        query.where('enabled')
+        UserSearchBase.__init__(self, prefs, query)
diff --git a/casemgr/version.py b/casemgr/version.py
new file mode 100644
index 0000000..e708b30
--- /dev/null
+++ b/casemgr/version.py
@@ -0,0 +1,22 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+__version__ = '1.8.4'
+try:
+    from svnrev import __svnrev__
+except ImportError:
+    __svnrev__ = ''
diff --git a/cocklebur/__init__.py b/cocklebur/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/cocklebur/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/cocklebur/agelib.py b/cocklebur/agelib.py
new file mode 100644
index 0000000..520cca1
--- /dev/null
+++ b/cocklebur/agelib.py
@@ -0,0 +1,292 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Date-of-birth & age handling
+"""
+from __future__ import division
+
+import re
+
+from mx import DateTime
+
+from cocklebur import datetime
+
+# Tim: Medical shorthand often uses "3/52" to mean 3 weeks, "4/12" to mean 4
+# months, "5/7" to mean 5 days, "13/24" to mean 13 hours, but not "43/60"
+# because that could be minutes or seconds. But 3wks or 3w, 4mths or 4m, 5d,
+# 13hrs etc are also used. 
+
+Error = datetime.Error
+
+PREC_YEAR = 366
+PREC_MONTH = 31
+PREC_WEEK = 7
+PREC_DAY = 1
+PREC_DOB = 0
+
+class Age(object):
+    """
+    An Age records an integer /age/ in /units/, where units is:
+        'y' for years
+        'm' for months
+        'w' for weeks
+        'd' for days
+    """
+    __slots__ = 'age', 'units'
+
+    def __init__(self, age, units):
+        if units not in 'ymwd':
+            raise ValueError('bad age units' % units)
+        if units == 'y' and (age < 0 or age >= 140):
+            raise Error('Age outside valid range: %s' % age)
+        self.age = int(age)
+        self.units = units
+
+    def __eq__(self, other):
+        if isinstance(other, Age):
+            return self.age == other.age and self.units == other.units
+        elif isinstance(other, tuple):
+            return (self.age, self.units) == other
+        return False
+
+    age_unit_abr = [
+        ('y', ('years', 'year', 'yrs', 'yr', 'y')),
+        ('m', ('months', 'month', 'mths', 'mth', 'm')),
+        ('w', ('weeks', 'week', 'wks', 'wk', 'w')),
+        ('d', ('days', 'day', 'd')),
+    ]
+    unit_abr_map = {}
+    for unit, abrs in age_unit_abr:
+        for abr in abrs:
+            unit_abr_map[abr] = unit
+
+    age_units_re = re.compile('(\d+)\s*(\D+)$')
+
+    denom_units = {
+        12: 'm',
+        52: 'w',
+        7: 'd'
+    }
+
+    def parse(cls, agestr):
+        """
+        Parse an age, for example 4/12 or 4m
+        """
+        try:
+            if not agestr:
+                raise ValueError
+            agestr = agestr.strip()
+            if '/' in agestr:
+                age, denom = agestr.split('/', 1)
+                age = int(age.rstrip())
+                units = cls.denom_units[int(denom.lstrip())]
+            else:
+                match = cls.age_units_re.match(agestr)
+                if match:
+                    age = int(match.group(1))
+                    units = cls.unit_abr_map[match.group(2).lower()]
+                else:
+                    age = int(agestr)
+                    units = 'y'
+            return cls(age, units)
+        except (KeyError, ValueError):
+            raise Error('Unknown age format: %r' % agestr)
+    parse = classmethod(parse)
+
+    def from_dob(cls, dob, now=None):
+        """
+        Make an Age from a date-of-birth
+        """
+        if hasattr(dob, 'mx'):
+            dob = dob.mx()
+        elif not isinstance(dob, DateTime.DateTimeType):
+            raise TypeError('bad date-of-birth type %r' % dob)
+        if now is None:
+            now = DateTime.now()
+        if dob > now:
+            raise Error('date-of-birth %s is in the future' % dob)
+        age = DateTime.Age(now, dob)
+        if age.years >= 3:
+            return cls(age.years + age.months / 12, 'y')
+        months = age.years * 12 + age.months + age.days / 30.5
+        if months >= 3:
+            return cls(months, 'm')
+        age = now - dob
+        if age.days > 30:
+            return cls(age.days / 7, 'w')
+        return cls(age.days, 'd')
+    from_dob = classmethod(from_dob)
+
+    def to_dobprec(self, now=None):
+        """
+        Given an integer age and unit code, return a DOB & precision
+        """
+        if self.units == 'y':
+            age = DateTime.RelativeDateTime(years=self.age)
+            prec = PREC_YEAR
+        elif self.units == 'm':
+            age = DateTime.RelativeDateTime(months=self.age)
+            prec = PREC_MONTH
+        elif self.units == 'w':
+            age = DateTime.RelativeDateTime(weeks=self.age)
+            prec = PREC_WEEK
+        elif self.units == 'd':
+            age = DateTime.RelativeDateTime(days=self.age)
+            prec = PREC_DAY
+        else:
+            raise AssertionError('Bad age units: %s' % self.units)
+        if now is None:
+            now = datetime.now()
+        return now - age, prec
+
+    units_map = {
+        'y': ('year', 'years'),
+        'm': ('month', 'months'),
+        'w': ('week', 'weeks'),
+        'd': ('day', 'days'),
+    }
+
+    def __str__(self):
+        return '%.0f%s' % (self.age, self.units)
+
+    def friendly(self):
+        units = self.units_map[self.units][int(self.age) != 1]
+        return '%.0f %s' % (self.age, units)
+
+
+def agestr(dob, now=None):
+    """
+    Given a DOB return a string describing the age
+    """
+    if dob:
+        try:
+            return str(Age.from_dob(dob, now))
+        except Error:
+            return '??'
+
+
+def dob_if_dob(dob, prec):
+    """
+    If exact DOB is known, return as a string, otherwise return None
+    """
+    if dob and not prec:
+        return dob.strftime(datetime.mx_parse_date.format)
+
+
+def age_if_dob(dob):
+    """
+    If DOB (not age) string, return age string, otherwise return None
+    """
+    if dob:
+        try:
+            return str(Age.from_dob(datetime.mx_parse_date(dob)))
+        except Error:
+            pass
+
+
+def parse_dob_or_age(value, now=None):
+    """
+    Given a date-of-birth or age string, return a DOB & precision
+    """
+    if not value:
+        return None, None
+    try:
+        return Age.parse(value).to_dobprec(now)
+    except Error:
+        pass
+    try:
+        return datetime.mx_parse_date(value).mx(), PREC_DOB
+    except Error:
+        raise Error('Invalid DOB/age %r' % value)
+
+
+def dob_query(query, field, dob, prec):
+    """
+    Add a DOB clause to the supplied /query/. /field/ is the base column name.
+    """
+    if dob:
+        q = 'abs(%s-%%s::date) <= GREATEST(%s_prec,%%s)' % (field, field)
+        query.where(q, dob, prec)
+
+
+def dobage_str(dob, prec, now=None):
+    """
+    If exact DOB, return DOB and age, otherwise just return age string
+    """
+    if not dob:
+        return None
+    if isinstance(dob, basestring):
+        try:
+            dob, prec = parse_dob_or_age(dob)
+        except datetime.Error, e:
+            return '??' 
+    try:
+        age = Age.from_dob(dob, now)
+    except Error:
+        return '??'
+    if prec:
+        return age.friendly()
+    else:
+        dob = dob.strftime(datetime.mx_parse_date.format)
+        return '%s (%s)' % (dob, age)
+
+
+def from_db(row, field='DOB'):
+    """
+    Given an object /row/ with DOB+DOB_prec, return either a DOB string
+    or an age string.
+    """
+    dob = getattr(row, field)
+    if dob:
+        prec = getattr(row, field + '_prec')
+        if prec:
+            return str(Age.from_dob(dob))
+        else:
+            return dob.strftime(datetime.mx_parse_date.format)
+
+
+def to_db(value, row, field='DOB'):
+    """
+    Given either a DOB string or an age string, set DOB+DOB_prec on the 
+    object /row/ if the value is different.
+    """
+    if not value:
+        dob = None
+        prec = None
+    elif isinstance(value, basestring):
+        row_dob = getattr(row, field)
+        row_age = None
+        if row_dob:
+            row_age = Age.from_dob(row_dob)
+        try:
+            age = Age.parse(value)
+            if age == row_age:
+                return
+            dob, prec = age.to_dobprec()
+        except Error:
+            try:
+                dob = datetime.mx_parse_date(value).mx()
+                prec = PREC_DOB
+            except Error:
+                raise Error('Invalid DOB/age %r' % value)
+    else:
+        dob = value
+        prec = PREC_DOB
+    setattr(row, field, dob)
+    setattr(row, field+'_prec', prec)
diff --git a/cocklebur/checkdigit.py b/cocklebur/checkdigit.py
new file mode 100644
index 0000000..495ecc5
--- /dev/null
+++ b/cocklebur/checkdigit.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+def calc_checkdigit(id):
+    sum = 0
+    for i, d in enumerate(id):
+        sum += int(d) * (i + 1)
+    return str((sum % 11) % 10)
+
+def check_checkdigit(id_and_check):
+    invalid = False, None
+    try:
+        id = id_and_check[:-1]
+        check = id_and_check[-1]
+    except IndexError:
+        return invalid
+    try:
+        if calc_checkdigit(id) == check:
+            return True, int(id)
+    except ValueError:
+        pass
+    return invalid
+
+def add_checkdigit(id):
+    id = str(id)
+    return id + calc_checkdigit(id)
+
+def test():
+    import random
+    n = 100000
+    okay_count = 0
+    for i in xrange(n):
+        okay, id = check_checkdigit('%d' % random.randrange(1000000000))
+        if okay:
+            okay_count += 1
+    print '%.1f%% okay' % (okay_count * 100.0 / n)
diff --git a/cocklebur/compat.py b/cocklebur/compat.py
new file mode 100644
index 0000000..827102a
--- /dev/null
+++ b/cocklebur/compat.py
@@ -0,0 +1,42 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Paper over some of the differences between python versions
+try:
+    # Introduced in python 2.4, faster implementation in 2.5
+    set = set
+except NameError:
+    from sets import Set as set
+
+try:
+    # Introduced in python 2.4
+    sorted = sorted
+except NameError:
+    def sorted(iterable, cmp=None, key=None, reverse=False):
+        if key is None:
+            lst = list(iterable)
+            lst.sort(cmp)
+            if reverse:
+                lst.reverse()
+            return lst
+        else:
+            lst = [(key(val), idx, val) for idx, val in enumerate(iterable)]
+            lst.sort(cmp)
+            if reverse:
+                lst.reverse()
+            return [i[-1] for i in lst]
diff --git a/cocklebur/countries.py b/cocklebur/countries.py
new file mode 100644
index 0000000..29f76fd
--- /dev/null
+++ b/cocklebur/countries.py
@@ -0,0 +1,280 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# The list of 2-character country codes and country names in this file were
+# obtained in May 2006 from the International Organization for Standardization
+# (ISO) web site at
+# http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/index.html
+# and are reproduced here for non-commercial purposes only in accordance with
+# the ISO usage terms.
+
+countries = [
+    ('AU', "AUSTRALIA"),
+    ('AF', "AFGHANISTAN"),
+    ('AX', "ALAND ISLANDS"),
+    ('AL', "ALBANIA"),
+    ('DZ', "ALGERIA"),
+    ('AS', "AMERICAN SAMOA"),
+    ('AD', "ANDORRA"),
+    ('AO', "ANGOLA"),
+    ('AI', "ANGUILLA"),
+    ('AQ', "ANTARCTICA"),
+    ('AG', "ANTIGUA AND BARBUDA"),
+    ('AR', "ARGENTINA"),
+    ('AM', "ARMENIA"),
+    ('AW', "ARUBA"),
+    ('AT', "AUSTRIA"),
+    ('AZ', "AZERBAIJAN"),
+    ('BS', "BAHAMAS"),
+    ('BH', "BAHRAIN"),
+    ('BD', "BANGLADESH"),
+    ('BB', "BARBADOS"),
+    ('BY', "BELARUS"),
+    ('BE', "BELGIUM"),
+    ('BZ', "BELIZE"),
+    ('BJ', "BENIN"),
+    ('BM', "BERMUDA"),
+    ('BT', "BHUTAN"),
+    ('BO', "BOLIVIA"),
+    ('BA', "BOSNIA AND HERZEGOVINA"),
+    ('BW', "BOTSWANA"),
+    ('BV', "BOUVET ISLAND"),
+    ('BR', "BRAZIL"),
+    ('IO', "BRITISH INDIAN OCEAN TERRITORY"),
+    ('BN', "BRUNEI DARUSSALAM"),
+    ('BG', "BULGARIA"),
+    ('BF', "BURKINA FASO"),
+    ('BI', "BURUNDI"),
+    ('KH', "CAMBODIA"),
+    ('CM', "CAMEROON"),
+    ('CA', "CANADA"),
+    ('CV', "CAPE VERDE"),
+    ('KY', "CAYMAN ISLANDS"),
+    ('CF', "CENTRAL AFRICAN REPUBLIC"),
+    ('TD', "CHAD"),
+    ('CL', "CHILE"),
+    ('CN', "CHINA"),
+    ('CX', "CHRISTMAS ISLAND"),
+    ('CC', "COCOS (KEELING) ISLANDS"),
+    ('CO', "COLOMBIA"),
+    ('KM', "COMOROS"),
+    ('CG', "CONGO"),
+    ('CD', "CONGO, THE DEMOCRATIC REPUBLIC OF THE"),
+    ('CK', "COOK ISLANDS"),
+    ('CR', "COSTA RICA"),
+    ('CI', "COTE D'IVOIRE"),
+    ('HR', "CROATIA"),
+    ('CU', "CUBA"),
+    ('CY', "CYPRUS"),
+    ('CZ', "CZECH REPUBLIC"),
+    ('DK', "DENMARK"),
+    ('DJ', "DJIBOUTI"),
+    ('DM', "DOMINICA"),
+    ('DO', "DOMINICAN REPUBLIC"),
+    ('EC', "ECUADOR"),
+    ('EG', "EGYPT"),
+    ('SV', "EL SALVADOR"),
+    ('GQ', "EQUATORIAL GUINEA"),
+    ('ER', "ERITREA"),
+    ('EE', "ESTONIA"),
+    ('ET', "ETHIOPIA"),
+    ('FK', "FALKLAND ISLANDS (MALVINAS)"),
+    ('FO', "FAROE ISLANDS"),
+    ('FJ', "FIJI"),
+    ('FI', "FINLAND"),
+    ('FR', "FRANCE"),
+    ('GF', "FRENCH GUIANA"),
+    ('PF', "FRENCH POLYNESIA"),
+    ('TF', "FRENCH SOUTHERN TERRITORIES"),
+    ('GA', "GABON"),
+    ('GM', "GAMBIA"),
+    ('GE', "GEORGIA"),
+    ('DE', "GERMANY"),
+    ('GH', "GHANA"),
+    ('GI', "GIBRALTAR"),
+    ('GR', "GREECE"),
+    ('GL', "GREENLAND"),
+    ('GD', "GRENADA"),
+    ('GP', "GUADELOUPE"),
+    ('GU', "GUAM"),
+    ('GT', "GUATEMALA"),
+    ('GG', "GUERNSEY"),
+    ('GW', "GUINEA-BISSAU"),
+    ('GN', "GUINEA"),
+    ('GY', "GUYANA"),
+    ('HT', "HAITI"),
+    ('HM', "HEARD ISLAND AND MCDONALD ISLANDS"),
+    ('VA', "HOLY SEE (VATICAN CITY STATE)"),
+    ('HN', "HONDURAS"),
+    ('HK', "HONG KONG"),
+    ('HU', "HUNGARY"),
+    ('IS', "ICELAND"),
+    ('IN', "INDIA"),
+    ('ID', "INDONESIA"),
+    ('IR', "IRAN, ISLAMIC REPUBLIC OF"),
+    ('IQ', "IRAQ"),
+    ('IE', "IRELAND"),
+    ('IM', "ISLE OF MAN"),
+    ('IL', "ISRAEL"),
+    ('IT', "ITALY"),
+    ('JM', "JAMAICA"),
+    ('JP', "JAPAN"),
+    ('JE', "JERSEY"),
+    ('JO', "JORDAN"),
+    ('KZ', "KAZAKHSTAN"),
+    ('KE', "KENYA"),
+    ('KI', "KIRIBATI"),
+    ('KP', "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF"),
+    ('KR', "KOREA, REPUBLIC OF"),
+    ('KW', "KUWAIT"),
+    ('KG', "KYRGYZSTAN"),
+    ('LA', "LAO PEOPLE'S DEMOCRATIC REPUBLIC"),
+    ('LV', "LATVIA"),
+    ('LB', "LEBANON"),
+    ('LS', "LESOTHO"),
+    ('LR', "LIBERIA"),
+    ('LY', "LIBYAN ARAB JAMAHIRIYA"),
+    ('LI', "LIECHTENSTEIN"),
+    ('LT', "LITHUANIA"),
+    ('LU', "LUXEMBOURG"),
+    ('MO', "MACAO"),
+    ('MK', "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF"),
+    ('MG', "MADAGASCAR"),
+    ('MW', "MALAWI"),
+    ('MY', "MALAYSIA"),
+    ('MV', "MALDIVES"),
+    ('ML', "MALI"),
+    ('MT', "MALTA"),
+    ('MH', "MARSHALL ISLANDS"),
+    ('MQ', "MARTINIQUE"),
+    ('MR', "MAURITANIA"),
+    ('MU', "MAURITIUS"),
+    ('YT', "MAYOTTE"),
+    ('MX', "MEXICO"),
+    ('FM', "MICRONESIA, FEDERATED STATES OF"),
+    ('MD', "MOLDOVA, REPUBLIC OF"),
+    ('MC', "MONACO"),
+    ('MN', "MONGOLIA"),
+    ('MS', "MONTSERRAT"),
+    ('MA', "MOROCCO"),
+    ('MZ', "MOZAMBIQUE"),
+    ('MM', "MYANMAR"),
+    ('NA', "NAMIBIA"),
+    ('NR', "NAURU"),
+    ('NP', "NEPAL"),
+    ('AN', "NETHERLANDS ANTILLES"),
+    ('NL', "NETHERLANDS"),
+    ('NC', "NEW CALEDONIA"),
+    ('NZ', "NEW ZEALAND"),
+    ('NI', "NICARAGUA"),
+    ('NG', "NIGERIA"),
+    ('NE', "NIGER"),
+    ('NU', "NIUE"),
+    ('NF', "NORFOLK ISLAND"),
+    ('MP', "NORTHERN MARIANA ISLANDS"),
+    ('NO', "NORWAY"),
+    ('OM', "OMAN"),
+    ('PK', "PAKISTAN"),
+    ('PW', "PALAU"),
+    ('PS', "PALESTINIAN TERRITORY, OCCUPIED"),
+    ('PA', "PANAMA"),
+    ('PG', "PAPUA NEW GUINEA"),
+    ('PY', "PARAGUAY"),
+    ('PE', "PERU"),
+    ('PH', "PHILIPPINES"),
+    ('PN', "PITCAIRN"),
+    ('PL', "POLAND"),
+    ('PT', "PORTUGAL"),
+    ('PR', "PUERTO RICO"),
+    ('QA', "QATAR"),
+    ('RE', "REUNION"),
+    ('RO', "ROMANIA"),
+    ('RU', "RUSSIAN FEDERATION"),
+    ('RW', "RWANDA"),
+    ('SH', "SAINT HELENA"),
+    ('KN', "SAINT KITTS AND NEVIS"),
+    ('LC', "SAINT LUCIA"),
+    ('PM', "SAINT PIERRE AND MIQUELON"),
+    ('VC', "SAINT VINCENT AND THE GRENADINES"),
+    ('WS', "SAMOA"),
+    ('SM', "SAN MARINO"),
+    ('ST', "SAO TOME AND PRINCIPE"),
+    ('SA', "SAUDI ARABIA"),
+    ('SN', "SENEGAL"),
+    ('CS', "SERBIA AND MONTENEGRO"),
+    ('SC', "SEYCHELLES"),
+    ('SL', "SIERRA LEONE"),
+    ('SG', "SINGAPORE"),
+    ('SK', "SLOVAKIA"),
+    ('SI', "SLOVENIA"),
+    ('SB', "SOLOMON ISLANDS"),
+    ('SO', "SOMALIA"),
+    ('ZA', "SOUTH AFRICA"),
+    ('GS', "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS"),
+    ('ES', "SPAIN"),
+    ('LK', "SRI LANKA"),
+    ('SD', "SUDAN"),
+    ('SR', "SURINAME"),
+    ('SJ', "SVALBARD AND JAN MAYEN"),
+    ('SZ', "SWAZILAND"),
+    ('SE', "SWEDEN"),
+    ('CH', "SWITZERLAND"),
+    ('SY', "SYRIAN ARAB REPUBLIC"),
+    ('TW', "TAIWAN, PROVINCE OF CHINA"),
+    ('TJ', "TAJIKISTAN"),
+    ('TZ', "TANZANIA, UNITED REPUBLIC OF"),
+    ('TH', "THAILAND"),
+    ('TL', "TIMOR-LESTE"),
+    ('TG', "TOGO"),
+    ('TK', "TOKELAU"),
+    ('TO', "TONGA"),
+    ('TT', "TRINIDAD AND TOBAGO"),
+    ('TN', "TUNISIA"),
+    ('TR', "TURKEY"),
+    ('TM', "TURKMENISTAN"),
+    ('TC', "TURKS AND CAICOS ISLANDS"),
+    ('TV', "TUVALU"),
+    ('UG', "UGANDA"),
+    ('UA', "UKRAINE"),
+    ('AE', "UNITED ARAB EMIRATES"),
+    ('GB', "UNITED KINGDOM"),
+    ('UM', "UNITED STATES MINOR OUTLYING ISLANDS"),
+    ('US', "UNITED STATES"),
+    ('UY', "URUGUAY"),
+    ('UZ', "UZBEKISTAN"),
+    ('VU', "VANUATU"),
+    ('VE', "VENEZUELA"),
+    ('VN', "VIET NAM"),
+    ('VG', "VIRGIN ISLANDS, BRITISH"),
+    ('VI', "VIRGIN ISLANDS, U.S."),
+    ('WF', "WALLIS AND FUTUNA"),
+    ('EH', "WESTERN SAHARA"),
+    ('YE', "YEMEN"),
+    ('ZM', "ZAMBIA"),
+    ('ZW', "ZIMBABWE"),
+]
+
+country_map = dict(countries)
+
+country_optionexpr = list(countries)
+country_optionexpr.insert(0, ('', 'Unknown'))
+
+search_country_optionexpr = list(countries)
+search_country_optionexpr.insert(0, ('!', 'Unknown'))
+search_country_optionexpr.insert(0, ('', 'Any'))
+
diff --git a/cocklebur/daemonize.py b/cocklebur/daemonize.py
new file mode 100644
index 0000000..6da5c81
--- /dev/null
+++ b/cocklebur/daemonize.py
@@ -0,0 +1,42 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+
+def daemonize():
+    """
+    Background and dissociate from controlling tty's - this is very much
+    unix specific. Refer to Stevens' "Advanced Programming in the
+    Unix Environment"
+    """
+    # Background
+    pid = os.fork()
+    if pid:
+        os.waitpid(pid, 0)      # Wait for intermedite process - avoid zombie
+        return True # Parent
+    # Child
+    os.setsid()
+    if os.fork():
+        os._exit(0)             # Ensure we aren't process group leader
+    devnull = os.open("/dev/null", os.O_RDWR)
+    os.close(0)
+    os.close(1)
+    os.dup2(devnull, 0)
+    os.dup2(devnull, 1)
+    os.close(devnull)
+    return False
diff --git a/cocklebur/datetime.py b/cocklebur/datetime.py
new file mode 100644
index 0000000..2311a45
--- /dev/null
+++ b/cocklebur/datetime.py
@@ -0,0 +1,528 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard Library
+import time
+import operator
+import re
+
+# 3rd Party
+from mx import DateTime
+
+# Microcomputers have been around for more than 3 decades, yet here
+# we are still writing our own date/time parsing routines. Sigh.
+
+datestyle = None
+
+class Error(Exception):
+    pass
+
+
+def fix_year(year):
+    if year < 100:
+        this_year = time.localtime().tm_year
+        this_year_2digit = this_year % 100
+        if year > this_year_2digit + 5:
+            year -= 100;
+        year += this_year - this_year_2digit
+    return year
+
+def to_fmt(spec):
+    """
+    Convert arbitrary YYYY-MM-DD hh:mm:ss style date specs into
+    strptime-style formats.
+    """
+    spec = spec.replace('YYYY', '%Y')
+    spec = spec.replace('YY', '%y')
+    spec = spec.replace('MMM', '%b')
+    spec = spec.replace('MM', '%m')
+    spec = spec.replace('DD', '%d')
+    spec = spec.replace('hh', '%H')
+    spec = spec.replace('mm', '%M')
+    spec = spec.replace('ss', '%S')
+    return spec
+
+class fmt_parser(object):
+
+    __slots__ = 'fmt', 'fix_year', 'year_adj', 'year_cutoff'
+
+    def __init__(self, fmt, age_years=False):
+        self.fmt = to_fmt(fmt)
+        self.fix_year = '%y' in self.fmt and age_years
+        if self.fix_year:
+            self.year_adj = DateTime.RelativeDate(years=100)
+            self.year_cutoff = DateTime.today().year + 1
+
+    def __call__(self, value):
+        date = mx_parse_datetime.strptime(value, self.fmt)
+        if self.fix_year and date.year > self.year_cutoff:
+            return date - self.year_adj
+        return date
+
+
+def parse_date(date_str):
+    """
+    Parse a date in dd/mm/{yy}yy, dd-mm-{yy}yy, or ISO yyyy-mm-dd
+    form, and returns a 3-tuple (year, month, day).
+
+    Our century guessing is tuned for human lifespans - we accept
+    2 digit dates up to 5 years in the future, anything else is
+    considered to be the previous century.
+
+    Raises Error if a parsing error occurs.
+    """
+    try:
+        try:
+            day, month, year = date_str.split('/')
+        except ValueError:
+            day, month, year = date_str.split('-')
+        day, month, year = int(day), int(month), int(year)
+        if day > 1000:
+            # ISO date
+            year, month, day = day, month, year
+        else:
+            if datestyle == 'MDY':
+                day, month = month, day
+            if year < 100:
+                year = fix_year(year)
+        return year, month, day
+    except (ValueError, AttributeError):
+        raise Error('could not parse date "%s"' % date_str)
+
+def parse_time(time_str):
+    """
+    Parse a time in hh:mm or hh:mm:ss form, and returns a 3-tuple
+    (hour, minute, second).
+
+    Raises Error if a parsing error occurs.
+    """
+    try:
+        fields = time_str.split(':', 2)
+        if len(fields) < 3:
+            # Just hours and minutes
+            fields.append(0)
+        else:
+            # Remove microseconds if it's included
+            fields[-1] = fields[-1].split('.')[0]
+        hours, minutes, seconds = map(int, fields)
+        if hours < 0 or hours > 23: raise ValueError
+        if minutes < 0 or hours > 59: raise ValueError
+        if seconds < 0 or seconds > 59: raise ValueError
+        return hours, minutes, seconds
+    except (ValueError, AttributeError):
+        raise Error('could not parse time "%s" - use HH:MM:SS format' % time_str)
+
+def parse_datetime(arg):
+    try:
+        try:
+            p1, p2 = arg.split()
+        except ValueError:
+            return parse_date(arg) + (0,0,0)
+        else:
+            try:
+                return parse_date(p1) + parse_time(p2)
+            except Error:
+                # Transposed?
+                return parse_date(p2) + parse_time(p1)
+    except Error:
+        raise Error('could not parse date/time "%s"' % arg)
+
+
+# We really just want to subclass mx.DateTime.DateTime, but it's an old-style
+# extension type, so we have to proxy instead - this causes a fair bit of
+# phutzing around overloading operators.
+class DatetimeFormat(object):
+    __slots__ = '_value'
+
+    def __str__(self):
+        if self._value is None:
+            return ''
+        else:
+            return self.strftime(self.format)
+
+    def __repr__(self):
+        if self._value is None:
+            value = None
+        else:
+            value = str(self)
+        return '<%s.%s %s>' % (self.__class__.__module__,
+                               self.__class__.__name__,
+                               value)
+    def strptime(cls, t, fmt):
+        try:
+            return cls(DateTime.strptime(t, fmt))
+        except DateTime.Error, e:
+            raise Error('date/time %r does not match format %r' % (t, fmt))
+        except ValueError, e:
+            raise Error(str(e))
+    strptime = classmethod(strptime)
+
+    def mx(self):
+        return self._value
+
+    def __getattr__(self, a):
+        return getattr(self._value, a)
+
+    def __setattr__(self, a, v):
+        if a == '_value':
+            object.__setattr__(self, a, v)
+        else:
+            setattr(self._value, a, v)
+
+    def __getstate__(self):
+        return self._value,
+
+    def __setstate__(self, state):
+        self._value, = state
+
+    def __nonzero__(self):
+        return self._value is not None
+
+    def __add__(self, other):
+        return self._value + other
+
+    def __sub__(self, other):
+        return self._value - other
+
+    def __radd__(self, other):
+        return other + self._value
+
+    def __rsub__(self, other):
+        return other - self._value
+
+    def _ops(self, op, other):
+        if isinstance(other, basestring):
+            try:
+                other = self.__class__(other)
+            except Error:
+                pass
+        if isinstance(other, DatetimeFormat):
+            other = other._value
+        if self._value is None or other is None:
+            return op(self._value, other)
+        try:
+            return op(DateTime.cmp(self._value, other, self.precision), 0)
+        except TypeError, e:
+            raise TypeError('%s: %r vs %r' % (e, self._value, other))
+
+    def __eq__(self, other):
+        return self._ops(operator.eq, other)
+
+    def __ne__(self, other):
+        return self._ops(operator.ne, other)
+
+    def __gt__(self, other):
+        return self._ops(operator.gt, other)
+
+    def __ge__(self, other):
+        return self._ops(operator.ge, other)
+
+    def __lt__(self, other):
+        return self._ops(operator.lt, other)
+
+    def __le__(self, other):
+        return self._ops(operator.le, other)
+
+class mx_parse_date(DatetimeFormat):
+    __slots__ = ()
+    format = None
+    help = None
+    precision = 24 * 60 * 60 - 1
+
+    def __init__(self, arg):
+        if isinstance(arg, DateTime.DateTimeType):
+            self._value = arg
+        elif isinstance(arg, DatetimeFormat):
+            self._value = arg._value
+        elif not arg:
+            self._value = None
+        else:
+            try:
+                self._value = DateTime.Date(*parse_datetime(arg)[:3])
+            except Error:
+                raise Error('could not parse date "%s"' % arg)
+            except DateTime.Error:
+                raise Error('invalid date "%s"' % arg)
+    # python 2.2.1 overrides the normal MRO for these methods as a safety
+    # measure to prevent accidents with it's incomplete slots implementation.
+    __getstate__ = DatetimeFormat.__getstate__
+    __setstate__ = DatetimeFormat.__setstate__
+
+class mx_parse_time(DatetimeFormat):
+    __slots__ = ()
+    format = '%H:%M:%S'
+    help = 'hh:mm(:ss)'
+    precision = 1
+
+    def __init__(self, arg):
+        if isinstance(arg, DatetimeFormat):
+            arg = arg._value
+        if isinstance(arg, DateTime.DateTimeDeltaType):
+            self._value = arg
+        elif isinstance(arg, DateTime.DateTimeType):
+            self._value = DateTime.DateTimeDelta(0, arg.hour, arg.minute, 
+                                                    arg.second)
+        elif not arg:
+            self._value = None
+        else:
+            try:
+                self._value = DateTime.DateTimeDelta(0, *parse_time(arg))
+            except DateTime.Error:
+                raise Error('invalid time "%s"' % arg)
+        if self._value is not None and self._value.day:
+            raise Error('invalid time %r' % arg)
+    __getstate__ = DatetimeFormat.__getstate__
+    __setstate__ = DatetimeFormat.__setstate__
+
+class mx_parse_datetime(DatetimeFormat):
+    __slots__ = ()
+    format = None
+    help = None
+    precision = 1
+
+    def __init__(self, arg):
+        if isinstance(arg, DateTime.DateTimeType):
+            self._value = arg
+        elif isinstance(arg, DatetimeFormat):
+            self._value = arg._value
+        elif not arg:
+            self._value = None
+        else:
+            try:
+                self._value = DateTime.DateTime(*parse_datetime(arg))
+            except DateTime.Error:
+                raise Error('invalid date/time "%s"' % arg)
+    def date(self):
+        return mx_parse_date(self)
+    def time(self):
+        return mx_parse_time(self)
+    __getstate__ = DatetimeFormat.__getstate__
+    __setstate__ = DatetimeFormat.__setstate__
+
+def is_later_than(date_a, date_b):
+    if not date_a or not date_b:
+        return False
+    return date_a > date_b
+
+def now():
+    return mx_parse_datetime(DateTime.now())
+
+def relative(date, ref=None):
+    if isinstance(date, DateTime.DateTimeDeltaType):
+        delta = date
+    else:
+        if not date:
+            return ''
+        if ref is None:
+            ref = now()
+        delta = date - ref
+    past = delta < 0
+    if past:
+        delta = -delta
+    if delta.days > 400:
+        value = '%.0f years' % (delta.days / 365.24)
+    elif delta.days > 40:
+        value = '%.0f months' % (delta.days / 30.44)
+    elif delta.days > 13:
+        value = '%.0f weeks' % (delta.days / 7)
+    elif delta.days >= 2:
+        value = '%.0f days' % delta.days
+    elif delta.hours >= 2:
+        value = '%.0f hours' % delta.hours
+    elif round(delta.minutes) > 1:
+        value = '%.0f minutes' % delta.minutes
+    elif round(delta.minutes):
+        value = '1 minute'
+    else:
+        value = 'less than a minute'
+    if past:
+        return value + ' ago'
+    else:
+        return 'in ' + value
+
+days_of_week = {
+    'monday': DateTime.Monday,
+    'tuesday': DateTime.Tuesday,
+    'wednesday': DateTime.Wednesday,
+    'thursday': DateTime.Thursday,
+    'friday': DateTime.Friday,
+    'saturday': DateTime.Saturday,
+    'sunday': DateTime.Sunday,
+}
+
+def to_discrete(date, ref=None):
+    if isinstance(date, DateTime.DateTimeDeltaType):
+        delta = date
+    else:
+        if date is None:
+            return ''
+        if ref is None:
+            ref = now()
+        delta = date - ref
+    past = delta < 0
+    if past:
+        delta = -delta
+        raise ValueError('negative relative dates not currently supported')
+    if not delta:
+        return 'now'
+    elif delta.days < 1:
+        value = '%.0fh' % delta.hours
+    elif delta.days < 2:
+        value = 'tomorrow'
+    elif round(delta.days) == 7:
+        value = 'week'
+    elif round(delta.days) == 14:
+        value = 'fortnight'
+    elif delta.days < 30:
+        value = '%.0fd' % delta.days
+    elif delta.days <= 31:
+        value = 'month'
+    elif 88 < delta.days <= 92:
+        value = 'quarter'
+    else:
+        value = '%.0fm' % (delta.days / 30.44)
+    return value
+
+
+rel_date_re = re.compile(
+     '\s*'                                      # Ignore leading whitespace
+     '(now|tomorrow|yesterday'
+     '|monday|tuesday|wednesday|thursday|friday|saturday|sunday'
+     '|(\d*|one|two|three|four|five|six|seven|eight|nine|ten)'  # Count
+      '\s*'
+      '(h|d|w|m|y'                                              # Units
+      '|minutes?|hours?'
+      '|days?|weeks?|wks?|fortnights?|quarters?|months?|yrs?|years?'
+      '|/\d+'
+      ')'
+      '(\s*ago)?'                                               # Optional
+     ')'
+     '\s*'                                      # Ignore trailing whitespace
+     '$'
+    )
+
+count_map = {
+    '': 1,
+    None: 1,
+    'one': 1,
+    'two': 2,
+    'three': 3,
+    'four': 4,
+    'five': 5,
+    'six': 6,
+    'seven': 7,
+    'eight': 8,
+    'nine': 9,
+    'ten': 10,
+}
+
+def parse_discrete(relative, ref=None, past=False):
+    if not relative:
+        return None
+    match = rel_date_re.match(relative.lower())
+    if match:
+        daystart = dict(hour=7, minute=0, second=0)
+        if ref is None:
+            t = DateTime.now()
+        else:
+            t = ref
+        word, count, units, direction = match.groups()
+        count = count_map.get(count, count)
+        if word == 'now':
+            return mx_parse_datetime(t)
+        elif word in days_of_week:
+            day_of_week = days_of_week[word]
+            if day_of_week > t.day_of_week:
+                offs = DateTime.RelativeDate(weekday=(day_of_week, 0), **daystart)
+            else:
+                offs = DateTime.RelativeDate(weekday=(day_of_week, 0), days=7, **daystart)
+        elif word == 'tomorrow':
+            offs = DateTime.RelativeDate(days=1, **daystart)
+        elif word == 'yesterday':
+            offs = DateTime.RelativeDate(days=-1, **daystart)
+        elif units.startswith('minute'):
+            offs = DateTime.RelativeDate(minutes=int(count))
+        elif units[0] == 'h' or units == '/24':
+            offs = DateTime.RelativeDate(hours=int(count))
+        elif units[0] == 'd' or units == '/7':
+            offs = DateTime.RelativeDate(days=int(count))
+        elif units[0] == 'm' or units == '/12':
+            offs = DateTime.RelativeDate(months=int(count), **daystart)
+        elif units[0] == 'w' or units == '/52':
+            offs = DateTime.RelativeDate(weeks=int(count), **daystart)
+        elif units[0] == 'y':
+            offs = DateTime.RelativeDate(years=int(count), **daystart)
+        elif units.startswith('fortnight'):
+            offs = DateTime.RelativeDate(weeks=int(count)*2, **daystart)
+        elif relative == 'quarter':
+            offs = DateTime.RelativeDate(months=int(count)*3, **daystart)
+        if (direction and direction.lstrip() == 'ago') or past:
+            return mx_parse_datetime(t - offs)
+        else:
+            return mx_parse_datetime(t + offs)
+    else:
+        return mx_parse_datetime(relative)
+
+def near(a, b, range=60):
+    if not a or not b:
+        return False
+    delta = a - b
+    return abs(delta.seconds) < range
+
+
+iso_date_re = re.compile(r'(\d{4}-\d{2}-\d{2})')
+
+def date_fix(text):
+    """
+    Replace ISO dates in /text/ with the current date convention
+    """
+    if mx_parse_date.format != '%Y-%m-%d':
+        splits = iso_date_re.split(text)
+        for i in xrange(1, len(splits), 2):
+            try:
+                splits[i] = str(mx_parse_date(splits[i]))
+            except Error:
+                pass
+        text = ''.join(splits)
+    return text
+
+
+def set_date_style(new_datestyle):
+    global datestyle
+
+    old_datestyle = datestyle
+    if datestyle != new_datestyle:
+        if new_datestyle == 'DMY':
+            format = '%d/%m/%Y'
+            help = 'dd/mm/yyyy'
+        elif new_datestyle == 'MDY':
+            format = '%m/%d/%Y'
+            help = 'mm/dd/yyyy'
+        elif new_datestyle == 'ISO':
+            format = '%Y-%m-%d'
+            help = 'yyyy-mm-dd'
+        else:
+            raise Error('Unknown date format %r' % format)
+        datestyle = new_datestyle
+        mx_parse_date.format = format
+        mx_parse_date.help = help
+        mx_parse_datetime.format = format + ' ' + mx_parse_time.format
+        mx_parse_datetime.help = help + ' ' + mx_parse_time.help
+    return old_datestyle
+
+set_date_style('DMY')
diff --git a/cocklebur/dbobj/__init__.py b/cocklebur/dbobj/__init__.py
new file mode 100644
index 0000000..296cca7
--- /dev/null
+++ b/cocklebur/dbobj/__init__.py
@@ -0,0 +1,40 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# One gotchya worthy of note - we currently allow more than one primary key to
+# be declared - although this will prevent us creating the offending table (it
+# can still be done manually), updates and deletes will use the hybrid
+# "primary" key to locate the correct column to be updated.
+#
+# Cyclic dependancies be here (table describers to db describers, column
+# describers to table describers)!
+
+from cocklebur.dbobj.database_describer import *
+from cocklebur.dbobj.table_describer import *
+from cocklebur.dbobj.column_describer import *
+from cocklebur.dbobj.query_builder import *
+from cocklebur.dbobj.result import *
+from cocklebur.dbobj.execute import *
+from cocklebur.dbobj.cache import *
+from cocklebur.dbobj.misc import *
+from cocklebur.dbobj.dbapi import DatabaseError, DataError, OperationalError, \
+    IntegrityError, InternalError, ProgrammingError, NotSupportedError, \
+    IdentifierError, ValidationError, DuplicateKeyError, ConstraintError, \
+    TooManyRecords, RecordDeleted, \
+    TRUE, FALSE, \
+    Binary
diff --git a/cocklebur/dbobj/cache.py b/cocklebur/dbobj/cache.py
new file mode 100644
index 0000000..ecf82d4
--- /dev/null
+++ b/cocklebur/dbobj/cache.py
@@ -0,0 +1,46 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import time
+
+class RowCache:
+    """
+    A simple proxy to a dbobj.ResultRow object that periodically
+    refreshes itself.
+    """
+
+    def __init__(self, initial_row, ttl=60):
+        self.__row = initial_row
+        self.__when = time.time() + ttl
+        self.__ttl = ttl
+
+    def __refresh(self):
+        t = time.time()
+        if self.__when < t:
+            self.__when = t + self.__ttl
+            self.__row.db_refetch()
+
+    def __getattr__(self, a):
+        if a.startswith('_'):
+            raise AttributeError(a)
+        self.__refresh()
+        return getattr(self.__row, a)
+
+# Other ideas:
+#  - a "Query"/"ResultSet" proxy that periodically refreshes.
+#  - subsume table_dict?
diff --git a/cocklebur/dbobj/column_describer.py b/cocklebur/dbobj/column_describer.py
new file mode 100644
index 0000000..4caa7db
--- /dev/null
+++ b/cocklebur/dbobj/column_describer.py
@@ -0,0 +1,394 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+
+from mx import DateTime
+
+from cocklebur import datetime
+from cocklebur.dbobj import misc, table_extras, dbapi
+from cocklebur.dbobj.execute import execute
+
+
+class ColumnType:
+
+    primary_key = False
+    unique = False
+    default = None
+    obscure = False
+    auto_timestamp = False
+
+    def __init__(self, table_desc, name, **kwargs):
+        self.__dict__.update(kwargs)
+        self.table_desc = table_desc
+        self.name = name
+
+    def to_user(self, value):
+        """ Convert from internal representation to user representation """
+        return value
+
+    def from_user(self, value):
+        """ Convert from user representation to internal representation """
+        return value
+
+    def to_sql(self, value):
+        """ Convert from internal representation to sql representation """
+        return value
+
+    def from_sql(self, value):
+        """ Convert from sql representation to internal representation """
+        return value
+
+    def dependancies(self):
+        return []
+
+    def create_sql(self, col_sql):
+        col = [self.name, self.sql_type()]
+        if self.unique:
+            col.append('UNIQUE')
+        if self.default:
+            col.append('DEFAULT %s' % self.default)
+        col_sql.append(' '.join(col))
+
+    def initial_value(self):
+        return None
+
+    def reset_sequences(self, curs):
+        pass
+
+
+class StringColumn(ColumnType):
+
+    size = None
+
+    def to_sql(self, value):
+        if value and self.size and len(value) > self.size:
+            raise dbapi.ValidationError('%s: max field size is %s' %\
+                                        (self.name, self.size))
+        return value
+
+    def from_user(self, value):
+        if value is '':
+            return None
+        return value
+
+    def sql_type(self):
+        if self.size:
+            return 'VARCHAR(%d)' % self.size
+        else:
+            return 'TEXT'
+
+
+class PasswdColumn(StringColumn):
+    # At this time, a password column is just a string column that knows to
+    # obscure itself when being pretty-printed.
+    obscure = True
+
+
+class IntColumn(ColumnType):
+
+    def from_user(self, value):
+        if isinstance(value, basestring):
+            if not value:
+                return None
+            try:
+                return int(value)
+            except (ValueError, TypeError):
+                pass
+        return value
+
+    def to_sql(self, value):
+        if value is not None:
+            try:
+                value = int(value)
+            except ValueError:
+                raise dbapi.ValidationError('%s: field must be an integer' %
+                                            self.name)
+        return value
+
+    def sql_type(self):
+        return 'INTEGER'
+
+
+class SequenceExtra(table_extras.TableExtra):
+
+    needs_grant = True
+
+    def __init__(self, table_desc, column):
+        self.table_desc = table_desc
+        self.name = 'seq_%s_%s' % (column, self.table_desc.name)
+        self.column = column
+
+    def pre_create_sql(self):
+        if not self.table_desc.db.db_has_relation(self.name):
+            return 'CREATE SEQUENCE %s' % self.name
+
+#   OWNED BY introduced in PG 8.2
+#    def post_create_sql(self):
+#        if not self.table_desc.db.db_has_relation(self.name):
+#            return 'ALTER SEQUENCE %s OWNED BY %s.%s' %\
+#                (self.name, self.table_desc.name, self.column)
+
+    def post_drop_sql(self):
+        if self.table_desc.db.db_has_relation(self.name):
+            return 'DROP SEQUENCE %s' % self.name
+
+    def owner(self, curs, owner):
+        execute(curs, 'ALTER TABLE %s OWNER TO "%s"' % (self.name, owner))
+
+
+class SerialColumn(IntColumn): 
+
+    def __init__(self, table_desc, name, **kwargs):
+        ColumnType.__init__(self, table_desc, name, **kwargs)
+        self.seq = SequenceExtra(table_desc, self.name)
+        table_desc.extra(self.seq)
+
+    # Should really be bigint (64 bit)
+    #def sql_type(self):
+    #    return 'BIGINT'
+
+    def create_sql(self, col_sql):
+        self.default = "nextval('%s')" % self.seq.name
+        ColumnType.create_sql(self, col_sql)
+
+    def nextval_sql(self):
+        return "SELECT nextval('%s')" % self.seq.name
+
+    def reset_sequences(self, curs):
+        execute(curs, "SELECT setval('%s',(SELECT max(%s) FROM %s))" %\
+            (self.seq.name, self.name, self.table_desc.name))
+        curs.fetchone()
+
+class FloatColumn(ColumnType):
+
+    def from_user(self, value):
+        if isinstance(value, basestring):
+            if not value:
+                return None
+            try:
+                return float(value)
+            except (ValueError, TypeError):
+                pass
+        return value
+
+    def to_sql(self, value):
+        if value is not None:
+            try:
+                value = float(value)
+            except ValueError:
+                raise dbapi.ValidationError('%s: field must be a decimal number' % self.name)
+        return value
+
+    def sql_type(self):
+        return 'DOUBLE PRECISION'
+
+
+class BooleanColumn(ColumnType):
+
+    def from_user(self, value):
+        if value:
+            return True
+        return None
+
+    def to_sql(self, value):
+        if value:
+            return dbapi.TRUE
+        return dbapi.FALSE
+
+    def from_sql(self, value):
+        if value:
+            return True
+        return None
+
+    def to_user(self, value):
+        # We do this to work with HTML's <input type="checkbox"> - sorry
+        if value:
+            return 'True'
+        else:
+            return ''
+
+    def sql_type(self):
+        return 'BOOLEAN'
+
+    def initial_value(self):
+        if self.default and self.default[0].lower() == 't':
+            return True
+        return None
+
+
+class _DateTimeColumn(ColumnType):
+
+    pass
+
+class DateColumn(_DateTimeColumn):
+
+    def sql_type(self):
+        return 'DATE'
+
+    def from_user(self, value):
+        try:
+            return datetime.mx_parse_date(value)
+        except datetime.Error:
+            return value
+
+    def to_sql(self, value):
+        if value is None:
+            return None
+        elif isinstance(value, str):
+            try:
+                return datetime.mx_parse_date(value).mx()
+            except datetime.Error, e:
+                raise dbapi.ValidationError('%s: %s' % (self.name, e))
+        elif isinstance(value, DateTime.DateTimeType):
+            return value
+        elif isinstance(value, datetime.mx_parse_date):
+            return value.mx()
+        else:
+            raise TypeError('%s: bad column type: %s' % (self.name, type(value)))
+
+    def from_sql(self, value):
+        if value is not None:
+            return datetime.mx_parse_date(value)
+
+
+class TimeColumn(_DateTimeColumn):
+
+    def sql_type(self):
+        return 'TIME'
+
+    def from_user(self, value):
+        try:
+            return datetime.mx_parse_time(value)
+        except datetime.Error:
+            return value
+
+    def to_sql(self, value):
+        if value is None:
+            return None
+        elif isinstance(value, str):
+            try:
+                return datetime.mx_parse_time(value).mx()
+            except datetime.Error, e:
+                raise dbapi.ValidationError('%s: %s' % (self.name, e))
+        elif isinstance(value, DateTime.DateTimeDeltaType):
+            return value
+        elif isinstance(value, datetime.mx_parse_time):
+            return value.mx()
+        else:
+            raise TypeError('%s: bad column type: %s' % (self.name, type(value)))
+
+    def from_sql(self, value):
+        if value is not None:
+            return datetime.mx_parse_time(value)
+
+
+class DatetimeColumn(_DateTimeColumn):
+
+    def sql_type(self):
+        return 'TIMESTAMP'
+
+    def from_user(self, value):
+        try:
+            return datetime.mx_parse_datetime(value)
+        except datetime.Error:
+            return value
+
+    def to_sql(self, value):
+        if value is None:
+            return None
+        elif isinstance(value, str):
+            try:
+                return datetime.mx_parse_datetime(value).mx()
+            except datetime.Error, e:
+                raise dbapi.ValidationError('%s: %s' % (self.name, e))
+        elif isinstance(value, DateTime.DateTimeType):
+            return value
+        elif isinstance(value, datetime.mx_parse_datetime):
+            return value.mx()
+        else:
+            raise TypeError('%s: bad column type: %s' % (self.name, type(value)))
+
+    def from_sql(self, value):
+        if value is not None:
+            return datetime.mx_parse_datetime(value)
+
+
+class LastUpdateColumn(DatetimeColumn):
+    """
+    A timestamp column that auto-updates for the time of the last update
+
+    Note - special-cased in result.ResultRow
+    """
+    auto_timestamp = True
+
+
+class ReferenceColumn(ColumnType):
+
+    on_delete = None
+    on_update = None
+
+    def __init__(self, table_desc, name, **kwargs):
+        ColumnType.__init__(self, table_desc, name, **kwargs)
+        self.ref_col_desc = None
+        self.ref_column = None
+        self.ref_table, columns = misc.parse_tablecols(self.references)
+        if columns:
+            if len(columns) > 1:
+                raise ValueError('%s: ReferenceColumn cannot specify more than'
+                                 ' one target column: %s' % 
+                                 (self.name, ', '.join(columns)))
+            self.ref_column = columns[0]
+
+    def target_column(self):
+        if self.ref_col_desc is None:
+            ref_table_desc = self.table_desc.db.get_table(self.ref_table)
+            if not self.ref_column:
+                assert len(ref_table_desc.primary_keys) == 1
+                self.ref_col_desc = ref_table_desc.primary_keys[0]
+            else:
+                self.ref_col_desc = ref_table_desc.get_column(self.ref_column)
+        return self.ref_col_desc
+
+    def dependancies(self):
+        return [self.ref_table.lower()]
+
+    def sql_type(self):
+        ref_col_desc = self.target_column()
+        sql = ['%s REFERENCES %s(%s)' % 
+               (ref_col_desc.sql_type(), self.ref_table, ref_col_desc.name)]
+        if self.on_delete:
+            sql.append('ON DELETE %s' % self.on_delete)
+        if self.on_update:
+            sql.append('ON UPDATE %s' % self.on_update)
+        return ' '.join(sql)
+
+    def to_sql(self, value):
+        ref_col_desc = self.target_column()
+        return ref_col_desc.to_sql(value)
+
+
+class BinaryColumn(ColumnType):
+
+    size = None
+
+    def to_sql(self, value):
+        return dbapi.Binary(value)
+
+    def sql_type(self):
+        return 'BYTEA'
+
diff --git a/cocklebur/dbobj/database_describer.py b/cocklebur/dbobj/database_describer.py
new file mode 100644
index 0000000..35d70a8
--- /dev/null
+++ b/cocklebur/dbobj/database_describer.py
@@ -0,0 +1,474 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+import sys
+import time
+import cPickle
+import weakref
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur.dbobj import dbapi, query_builder, table_describer, result, \
+                            table_dict, participation_table, misc
+from cocklebur.dbobj.execute import execute, commit, rollback
+
+def order_by_dependancies(objs, debug = 0):
+    """ Order some collection of objects to satisfy dependancies """
+    needs = dict([(obj.name, (obj, obj.dependancies())) for obj in objs])
+    in_order = []
+    while needs:
+        did_work = False
+        if debug:
+            print 'needs: ', ','.join(['%s(%s)' % (name, ','.join(deps)) 
+                                       for name, (obj, deps) in needs.items()])
+        for name in needs.keys():
+            obj, depends = needs[name]  # Don't use .items() as we update
+            if not depends:
+                if debug: print "resolved:", name
+                did_work = True
+                in_order.append(obj)
+                del needs[name]
+                for obj, dependancies in needs.values():
+                    dependancies.discard(name)
+        if debug: print 'inorder: ', ','.join([obj.name for obj in in_order])
+        if not did_work:
+            errs = ["%s: %s" % (name, ','.join(depends))
+                    for name, (obj, depends) in needs.items()]
+            raise ValueError("Couldn't resolve dependancies for: " +
+                                  ', '.join(errs))
+    return in_order
+
+class DSN(str):
+    """
+    A string-like class for DSN's (Data Source Name)
+
+    host, port, dbname and user are accessable as R/O attributes -
+    other elements of the DSN (password) are not recorded.
+
+    Instances are intended to be trivialably hashable and small, 
+    hence subclass of str.
+    """
+
+#    __slots__ = ()
+    _index = {'host': 0, 'port': 1, 'database': 2, 'user': 3}
+
+    def __new__(cls, dsn = '', **kwargs):
+        params = dsn.split(':')[:4]
+        params.extend([''] * (4 - len(params)))
+        if kwargs:
+            for name, index in cls._index.items():
+                params[index] = str(kwargs.get(name, params[index]))
+        return str.__new__(cls, ':'.join(params))
+
+    def __getattr__(self, name):
+        try:
+            index = self._index[name]
+        except KeyError:
+            raise AttributeError('DSN object has no attribute %r' % name)
+        return self.split(':')[index]
+
+    def get_kw(self):
+        kw = {}
+        params = self.split(':')
+        for name, index in self._index.items():
+            param = params[index]
+            if param:
+                kw[name] = param
+        return kw
+
+    def connect(self):
+        connect_args = self.get_kw()
+        connect_args.update(dbapi.connect_extra)
+        return dbapi.connect(**connect_args)
+
+
+def template_exec(dsn, cmd):
+    """
+    Connect to the template1 database, retrying if necessary
+    """
+    template_dsn = DSN(dsn, database='template1')
+    db = template_dsn.connect()
+    try:
+        db.autocommit = True
+        curs = db.cursor()
+        n = 0
+        while 1:
+            try:
+                execute(curs, cmd)
+                break
+            except dbapi.DatabaseError, e:
+                if n < 3 and 'is being accessed by other users' in str(e):
+                    n += 1
+                    time.sleep(1)
+                    continue
+                else:
+                    raise
+    finally:
+        db.close()
+
+
+class DatabaseDescriberCore:
+    """
+    Describe a database
+
+    Instances of this class contain the DSN of the database,
+    as well as a description of the tables and associated
+    indexes and sequences. The class otherwise behaves as a
+    partial proxy to the DB-API 2.0 "db" object - specifically,
+    the cursor(), commit(), rollback() and close() methods.
+    """
+
+    def __init__(self, dsn = '', **kwargs):
+        if not isinstance(dsn, DSN):
+            dsn = DSN(dsn, **kwargs)
+        self.dsn = dsn
+        self.db = None
+        self.updated = True
+        self.filename = str(self.dsn)
+        self.mtime = 0
+        self.clear_pending()
+        self._invalidate_sys_info()
+        self.table_describers = {}
+
+    def _invalidate_sys_info(self):
+        self.__sys_relations = None
+
+    def _get_sys_info(self):
+        curs = self.cursor()
+        curs.execute('select relname from pg_class')
+        self.__sys_relations = dict([(r[0].lower(), True) 
+                                     for r in curs.fetchall()])
+
+    def load_describer(self, path = ''):
+        if path:
+            self.filename = os.path.join(path, str(self.dsn))
+        f = open(self.filename, 'rb')
+        st = os.fstat(f.fileno())
+        try:
+            if st.st_mtime > self.mtime:
+                self.mtime = st.st_mtime
+                self._invalidate_sys_info()
+                self.table_describers, = cPickle.load(f)
+                for table_desc in self.table_describers.values():
+                    table_desc.db = self
+                self.updated = False
+        finally:
+            f.close()
+
+    def save_describer(self, path = '', owner = None, mode = None):
+        if path:
+            self.filename = os.path.join(path, str(self.dsn))
+        if self.updated:
+            import tempfile
+
+            fd, tmpname = tempfile.mkstemp(dir = os.path.dirname(self.filename))
+            f = os.fdopen(fd, 'wb')
+            try:
+                # Break cycles prior to pickling
+                for table_desc in self.table_describers.values():
+                    table_desc.db = None
+                try:
+                    cPickle.dump((self.table_describers,), f, -1)
+                finally:
+                    for table_desc in self.table_describers.values():
+                        table_desc.db = self
+                self.mtime = os.fstat(f.fileno()).st_mtime
+                f.close()
+                if mode is None:
+                    mode = 0644
+                # We really want fchmod/fchown here for safety
+                os.chmod(tmpname, mode)
+                if owner is not None:
+                    os.chown(tmpname, *owner)
+                os.rename(tmpname, self.filename)
+                self.updated = False
+            finally:
+                try:
+                    os.unlink(tmpname)
+                except OSError:
+                    pass
+
+    def new_table(self, name, **kwargs):
+        name = name.lower()
+        table_desc = table_describer.TableDescriber(self, name, **kwargs)
+        self.table_describers[name] = table_desc
+        self.updated = True
+        return table_desc
+
+    def rename_table(self, old_name, new_name):
+        old_name = old_name.lower()
+        new_name = new_name.lower()
+        table_desc = self.get_table(old_name)
+        table_desc.rename(new_name)
+        del self.table_describers[old_name]
+        self.table_describers[new_name] = table_desc
+        self.updated = True
+        return table_desc
+        
+    def get_table(self, name):
+        try:
+            return self.table_describers[name.lower()]
+        except KeyError:
+            raise KeyError('unknown table describer "%s"' % name)
+
+    def has_table(self, name):
+        return name in self.table_describers
+
+    def get_tables(self):
+        return self.table_describers.values()
+
+    def query(self, table, **kwargs):
+        return query_builder.Query(self.get_table(table), **kwargs)
+
+    def table_dict(self, table, key_col = None):
+        return table_dict.TableDict(self.get_table(table), key_col)
+
+    def participation_table(self, table, master_col, slave_col):
+        return participation_table.ParticipationTable(self.get_table(table),
+                                                      master_col, slave_col)
+
+    def ptset(self, table, master_col, slave_col, key=None, filter=None):
+        return participation_table.ptset(self.get_table(table),
+                                         master_col, slave_col, key, filter)
+
+    def new_row(self, table, **seed_values):
+        return self.get_table(table).get_row(seed_values=seed_values)
+
+    def nextval(self, table, colname):
+        "Return the next value associated with a SERIAL column"
+        return self.get_table(table).nextval(colname)
+
+    def empty_result_set(self, table):
+        return result.ResultSet(self.get_table(table))
+
+    def make_table(self, table, **kw):
+        self.get_table(table).create(**kw)
+
+    def drop_table(self, table):
+        self.get_table(table).drop()
+        del self.table_describers[table]
+        self.updated = True
+
+    def make_user(self, username):
+        curs = self.cursor()
+        try:
+            execute(curs, 'SELECT usename FROM pg_user'
+                            ' WHERE usename=%s', (username,))
+            if not curs.fetchone():
+                execute(curs, 'CREATE USER "%s"' % username)
+                self.commit()
+        finally:
+            curs.close()
+
+    def create(self, owner=None, grant=None):
+        # Attempt to connect to the database, create it if it doesn't exist
+        try:
+            self._connect_db()
+        except dbapi.DatabaseError, e:
+            if 'does not exist' not in str(e):
+                raise
+            misc.valid_identifier(self.dsn.database, 'database name', 
+                                  strict=False)
+            template_exec(self.dsn, 'CREATE DATABASE %s' % self.dsn.database)
+        if grant:
+            self.make_user(grant)
+        if owner:
+            self.make_user(owner)
+
+    def make_database(self, owner=None, grant=None):
+        self.create(owner, grant)
+        # Reconnect to the (potentially newly created) database and create
+        # missing tables
+        table_descs = order_by_dependancies(self.table_describers.values())
+        for table_desc in table_descs:
+            table_desc.create(grant=grant, owner=owner)
+        self.commit()                    # Can't rollback CREATE DB anyway
+
+    def chown(self, owner):
+        curs = self.cursor()
+        try:
+            for table_desc in self.table_describers.values():
+                table_desc.owner(curs, owner)
+        finally:
+            curs.close()
+
+    def drop_all_tables(self, db):
+        """
+        Drops all the tables we know about. At this time, this method
+        is only used by the unittests to restore the schema to a known
+        state prior to running the next test.
+        """
+        assert self is db               # Paranoia
+        table_descs = order_by_dependancies(self.table_describers.values())
+        for table_desc in table_descs[::-1]:
+            table_desc.drop()
+        self.commit()
+        self._invalidate_sys_info()
+
+    def reset_sequences(self):
+        """
+	Scans all sequences, resetting them to the highest value
+	appearing in the associated table.
+        """
+        curs = self.db.cursor()
+        try:
+            for table_desc in self.table_describers.values():
+                table_desc.reset_sequences(curs)
+        finally:
+            curs.close()
+
+    def drop_database(self, db):
+        assert self is db               # Paranoia
+        self.unload()
+        try:
+            template_exec(self.dsn, 'DROP DATABASE %s' % self.dsn.database)
+        except dbapi.DatabaseError, e:
+            if 'does not exist' not in str(e):
+                raise
+
+    def db_has_relation(self, name):
+        if self.__sys_relations is None:
+            self._get_sys_info()
+        return self.__sys_relations.get(name.lower())
+
+    # Proxy to Database API connection objects
+    def _connect_db(self):
+        self.db = self.dsn.connect()
+        curs = self.db.cursor()
+        try:
+            execute(curs, "SET datestyle TO 'ISO,European'")
+# May potentially open us up to an SQL injection attack:
+# http://www.postgresql.org/docs/techdocs.50
+#            execute(curs, "SET client_encoding TO unicode")
+            execute(curs, "SET client_encoding TO SQL_ASCII")
+        finally:
+            curs.close()
+
+    def clear_pending(self):
+        self.pending = set()
+
+    def add_pending(self, row):
+        self.pending.add(weakref.ref(row))
+
+    def del_pending(self, row):
+        self.pending.discard(weakref.ref(row))
+
+    def commit(self):
+        self._invalidate_sys_info()
+        if self.db is not None:
+            commit(self.db)
+            for ref in list(self.pending):
+                row = ref()
+                if row:
+                    row.db_commit()
+            self.clear_pending()
+
+    def rollback(self):
+        if self.db is not None:
+            rollback(self.db)
+            for ref in list(self.pending):
+                row = ref()
+                if row:
+                    row.db_rollback()
+            self.clear_pending()
+
+    def cursor(self):
+        if self.db is None:
+            self._connect_db()
+            return self.db.cursor()
+        try:
+            return self.db.cursor()
+        except dbapi.OperationalError, e:
+            print >> sys.stderr, 'RETRYING - %s' % e
+            self.close()
+            self._connect_db()
+            return self.db.cursor()
+
+    def close(self):
+        if self.db is not None:
+            self.clear_pending()
+            self.db.close()
+            self.db = None
+#            import traceback
+#            traceback.print_stack()
+
+    def unload(self):
+        if self.db is not None:
+            self.close()
+
+    def lock_table(self, table, mode, wait=True):
+        cmd = 'LOCK %s IN %s MODE' % (table, mode)
+        if not wait:
+            cmd += ' NOWAIT'
+        curs = self.cursor()
+        try:
+            execute(curs, cmd)
+        finally:
+            curs.close()
+
+
+class DatabaseDescriber(DatabaseDescriberCore):
+    """
+    We want pickles of this class to contain just a text representation
+    of the DSN, not the table describers and other support structures
+    (which are quite large), so that it can be saved to a browser hidden
+    field. The table describers are loaded from the local filesystem prior
+    to unpickling - the unpickled instance will refer to the previously
+    loaded instance.
+
+    We use a Borg pattern keyed by DSN to ensure there is
+    only one connection to a database per DSN, and one set of
+    table describers.
+
+    """
+    instances = {}
+
+    def __init__(self, dsn = '', **kwargs):
+        if not isinstance(dsn, DSN):
+            dsn = DSN(dsn, **kwargs)
+        try:
+            self._borg(dsn)
+        except KeyError:
+            DatabaseDescriber.instances[dsn] = self.__dict__
+            DatabaseDescriberCore.__init__(self, dsn)
+            self._invalidate_sys_info()
+
+    def _borg(self, dsn):
+        self.__dict__ = DatabaseDescriber.instances[dsn]
+
+    def __getstate__(self):
+        return self.dsn,
+
+    def __setstate__(self, state):
+        dsn, = state
+        self._borg(dsn)
+
+    def unload(self):
+        DatabaseDescriberCore.unload(self)
+        try:
+            del DatabaseDescriber.instances[self.dsn]
+        except KeyError:
+            pass
+
+
+def get_db(path, dsn):
+    db = DatabaseDescriber(dsn)
+    db.load_describer(path)
+    return db
diff --git a/cocklebur/dbobj/dbapi.py b/cocklebur/dbobj/dbapi.py
new file mode 100644
index 0000000..57499c8
--- /dev/null
+++ b/cocklebur/dbobj/dbapi.py
@@ -0,0 +1,45 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# What database prefers for boolean columns
+TRUE = True
+FALSE = False
+# Extra arguments to pass to connect()
+connect_extra = dict()
+
+try:
+    from ocpgdb import *
+    connect_extra = dict(use_mx_datetime=True)
+except ImportError:
+    from pyPgSQL import PgSQL
+    PgSQL.useUTCtimeValue = True       # Works around brokeness in some vers
+    PgSQL.fetchReturnsList = True      # faster, and duplicates dbobj work
+    from pyPgSQL.PgSQL import *
+    Binary = PgBytea
+    # pyPgSQL predates python True and False
+    TRUE = PG_True
+    FALSE = PG_False
+
+# Some fine-grained exceptions. Not part of the API, but this is a convenient
+# place to define them.
+class IdentifierError(DatabaseError): pass
+class ValidationError(DatabaseError): pass
+class DuplicateKeyError(OperationalError): pass
+class ConstraintError(OperationalError): pass
+class TooManyRecords(OperationalError): pass
+class RecordDeleted(OperationalError): pass
diff --git a/cocklebur/dbobj/exec_timing.py b/cocklebur/dbobj/exec_timing.py
new file mode 100644
index 0000000..e20d7e2
--- /dev/null
+++ b/cocklebur/dbobj/exec_timing.py
@@ -0,0 +1,57 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+class ExecCmdTiming(object):
+
+    __slots__ = 'total', 'count'
+
+    def __init__(self):
+        self.total = 0.0
+        self.count = 0
+
+    def record(self, el):
+        self.total += el
+        self.count += 1
+
+
+class ExecTiming:
+
+    def __init__(self):
+        self.timings = {}
+
+    def record(self, cmd, el):
+        try:
+            ect = self.timings[cmd]
+        except KeyError:
+            ect = self.timings[cmd] = ExecCmdTiming()
+        ect.record(el)
+
+    def __str__(self):
+        report = ['Top 20 queries (av time, freq, cmd):']
+        timings = [(ect.total / ect.count, ect.count, cmd)
+                   for cmd, ect in self.timings.iteritems()]
+        timings.sort()
+        for avg, freq, cmd in timings[-1:-20:-1]:
+            report.append('   %.3fs %4d %s' % (avg, freq, cmd))
+        return '\n'.join(report)
+
+exec_timing = ExecTiming()
+
+def show():
+    import sys
+    print >> sys.stderr, exec_timing
diff --git a/cocklebur/dbobj/execute.py b/cocklebur/dbobj/execute.py
new file mode 100644
index 0000000..0de420d
--- /dev/null
+++ b/cocklebur/dbobj/execute.py
@@ -0,0 +1,94 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+from time import time
+from cocklebur.dbobj import dbapi
+
+debug = False
+timing = False
+
+def execute_debug(value):
+    global debug
+    #if value:
+    #    import traceback
+    #    traceback.print_stack()
+    debug = value
+
+def execute_timing(value):
+    global timing, exec_timing
+    timing = value
+    if value:
+        from exec_timing import exec_timing
+
+prefix = '+'
+
+def execute(curs, cmd, args=()):
+    def pretty_cmd(cmd, args):
+        cleanargs = []
+        for arg in args:
+            if hasattr(arg, 'strftime'):
+                arg = str(arg)
+            else:
+                arg = repr(arg)
+            if len(arg) > 30:
+                arg = arg[:15] + '...' + arg[-10:]
+            cleanargs.append(arg)
+        if cleanargs:
+            cmdargs = cmd % tuple(cleanargs)
+        else:
+            cmdargs = cmd
+        return prefix + cmdargs.replace('\n', '\n' + prefix)
+
+    if ';' in cmd:      # Crude security hack
+        raise dbapi.ProgrammingError('Only one command per execute() allowed')
+    if debug or timing:
+        st = time()
+    try:
+        res = curs.execute(cmd, args)
+    except dbapi.Error, e:
+        exc_type, exc_value, exc_tb = sys.exc_info()
+        if 'duplicate key' in str(exc_value):
+            exc_type = dbapi.DuplicateKeyError
+        elif 'violates foreign key constraint' in str(exc_value):
+            exc_type = dbapi.ConstraintError
+#        sys.stderr.write('dbapi.error: %s: %s\n' % (e, pretty_cmd(cmd, args)))
+        try:
+            raise exc_type, '%s%s' % (exc_value, pretty_cmd(cmd, args)), exc_tb
+        finally:
+            del exc_type, exc_value, exc_tb
+    except Exception:
+        sys.stderr.write('Exception SQL: %s\n' % pretty_cmd(cmd, args))
+        raise
+    else:
+        if debug or timing:
+            el = time() - st
+        if debug: 
+            sys.stderr.write(pretty_cmd(cmd, args) + (' (%.3f secs)\n' % el))
+        if timing:
+            exec_timing.record(cmd, el)
+        return res
+
+def commit(db):
+    if debug:
+        sys.stderr.write(prefix + 'COMMIT\n')
+    db.commit()
+
+def rollback(db):
+    if debug:
+        sys.stderr.write(prefix + 'ROLLBACK\n')
+    db.rollback()
diff --git a/cocklebur/dbobj/misc.py b/cocklebur/dbobj/misc.py
new file mode 100644
index 0000000..f7e0f52
--- /dev/null
+++ b/cocklebur/dbobj/misc.py
@@ -0,0 +1,181 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur.dbobj import dbapi
+
+# These are the *PostGreSQL* reserved words from PG 7.4
+pg_keywords = set([
+    'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC',
+    'BOTH', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'CONSTRAINT',
+    'CREATE', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
+    'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO',
+    'ELSE', 'END', 'EXCEPT', 'FALSE', 'FOR', 'FOREIGN', 'FROM', 'GRANT',
+    'GROUP', 'HAVING', 'INITIALLY', 'INTERSECT', 'INTO', 'LEADING',
+    'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NEW', 'NOT', 'NULL', 'OFF',
+    'OFFSET', 'OLD', 'ON', 'ONLY', 'OR', 'ORDER', 'PLACING', 'PRIMARY',
+    'REFERENCES', 'SELECT', 'SESSION_USER', 'SOME', 'TABLE', 'THEN',
+    'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'WHEN',
+    'WHERE'
+])
+
+sql_keywords = set([
+    'ABORT', 'ABS', 'ABSOLUTE', 'ACCESS', 'ACTION', 'ADA', 'ADD',
+    'ADMIN', 'AFTER', 'AGGREGATE', 'ALIAS', 'ALL', 'ALLOCATE', 'ALTER',
+    'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARE', 'ARRAY', 'AS', 'ASC',
+    'ASENSITIVE', 'ASSERTION', 'ASSIGNMENT', 'ASYMMETRIC', 'AT',
+    'ATOMIC', 'AUTHORIZATION', 'AVG', 'BACKWARD', 'BEFORE', 'BEGIN',
+    'BETWEEN', 'BIGINT', 'BINARY', 'BIT', 'BITVAR', 'BIT_LENGTH',
+    'BLOB', 'BOOLEAN', 'BOTH', 'BREADTH', 'BY', 'C', 'CACHE',
+    'CALL', 'CALLED', 'CARDINALITY', 'CASCADE', 'CASCADED', 'CASE',
+    'CAST', 'CATALOG', 'CATALOG_NAME', 'CHAIN', 'CHAR', 'CHARACTER',
+    'CHARACTERISTICS', 'CHARACTER_LENGTH', 'CHARACTER_SET_CATALOG',
+    'CHARACTER_SET_NAME', 'CHARACTER_SET_SCHEMA', 'CHAR_LENGTH',
+    'CHECK', 'CHECKED', 'CHECKPOINT', 'CLASS', 'CLASS_ORIGIN', 'CLOB',
+    'CLOSE', 'CLUSTER', 'COALESCE', 'COBOL', 'COLLATE', 'COLLATION',
+    'COLLATION_CATALOG', 'COLLATION_NAME', 'COLLATION_SCHEMA', 'COLUMN',
+    'COLUMN_NAME', 'COMMAND_FUNCTION', 'COMMAND_FUNCTION_CODE', 'COMMENT',
+    'COMMIT', 'COMMITTED', 'COMPLETION', 'CONDITION_NUMBER', 'CONNECT',
+    'CONNECTION', 'CONNECTION_NAME', 'CONSTRAINT', 'CONSTRAINTS',
+    'CONSTRAINT_CATALOG', 'CONSTRAINT_NAME', 'CONSTRAINT_SCHEMA',
+    'CONSTRUCTOR', 'CONTAINS', 'CONTINUE', 'CONVERSION', 'CONVERT',
+    'COPY', 'CORRESPONDING', 'COUNT', 'CREATE', 'CREATEDB', 'CREATEUSER',
+    'CROSS', 'CUBE', 'CURRENT', 'CURRENT_DATE', 'CURRENT_PATH',
+    'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER',
+    'CURSOR', 'CURSOR_NAME', 'CYCLE', 'DATA', 'DATABASE', 'DATE',
+    'DATETIME_INTERVAL_CODE', 'DATETIME_INTERVAL_PRECISION', 'DAY',
+    'DEALLOCATE', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DEFAULTS',
+    'DEFERRABLE', 'DEFERRED', 'DEFINED', 'DEFINER', 'DELETE', 'DELIMITER',
+    'DELIMITERS', 'DEPTH', 'DEREF', 'DESC', 'DESCRIBE', 'DESCRIPTOR',
+    'DESTROY', 'DESTRUCTOR', 'DETERMINISTIC', 'DIAGNOSTICS', 'DICTIONARY',
+    'DISCONNECT', 'DISPATCH', 'DISTINCT', 'DO', 'DOMAIN', 'DOUBLE',
+    'DROP', 'DYNAMIC', 'DYNAMIC_FUNCTION', 'DYNAMIC_FUNCTION_CODE',
+    'EACH', 'ELSE', 'ENCODING', 'ENCRYPTED', 'END', 'END-EXEC', 'EQUALS',
+    'ESCAPE', 'EVERY', 'EXCEPT', 'EXCEPTION', 'EXCLUDING', 'EXCLUSIVE',
+    'EXEC', 'EXECUTE', 'EXISTING', 'EXISTS', 'EXPLAIN', 'EXTERNAL',
+    'EXTRACT', 'FALSE', 'FETCH', 'FINAL', 'FIRST', 'FLOAT', 'FOR',
+    'FORCE', 'FOREIGN', 'FORTRAN', 'FORWARD', 'FOUND', 'FREE', 'FREEZE',
+    'FROM', 'FULL', 'FUNCTION', 'G', 'GENERAL', 'GENERATED', 'GET',
+    'GLOBAL', 'GO', 'GOTO', 'GRANT', 'GRANTED', 'GROUP', 'GROUPING',
+    'HANDLER', 'HAVING', 'HIERARCHY', 'HOLD', 'HOST', 'HOUR', 'IDENTITY',
+    'IGNORE', 'ILIKE', 'IMMEDIATE', 'IMMUTABLE', 'IMPLEMENTATION',
+    'IMPLICIT', 'IN', 'INCLUDING', 'INCREMENT', 'INDEX', 'INDICATOR',
+    'INFIX', 'INHERITS', 'INITIALIZE', 'INITIALLY', 'INNER', 'INOUT',
+    'INPUT', 'INSENSITIVE', 'INSERT', 'INSTANCE', 'INSTANTIABLE',
+    'INSTEAD', 'INT', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO',
+    'INVOKER', 'IS', 'ISNULL', 'ISOLATION', 'ITERATE', 'JOIN', 'K', 'KEY',
+    'KEY_MEMBER', 'KEY_TYPE', 'LANCOMPILER', 'LANGUAGE', 'LARGE', 'LAST',
+    'LATERAL', 'LEADING', 'LEFT', 'LENGTH', 'LESS', 'LEVEL', 'LIKE',
+    'LIMIT', 'LISTEN', 'LOAD', 'LOCAL', 'LOCALTIME', 'LOCALTIMESTAMP',
+    'LOCATION', 'LOCATOR', 'LOCK', 'LOWER', 'M', 'MAP', 'MATCH', 'MAX',
+    'MAXVALUE', 'MESSAGE_LENGTH', 'MESSAGE_OCTET_LENGTH', 'MESSAGE_TEXT',
+    'METHOD', 'MIN', 'MINUTE', 'MINVALUE', 'MOD', 'MODE', 'MODIFIES',
+    'MODIFY', 'MODULE', 'MONTH', 'MORE', 'MOVE', 'MUMPS', 'NAME',
+    'NAMES', 'NATIONAL', 'NATURAL', 'NCHAR', 'NCLOB', 'NEW', 'NEXT',
+    'NO', 'NOCREATEDB', 'NOCREATEUSER', 'NONE', 'NOT', 'NOTHING',
+    'NOTIFY', 'NOTNULL', 'NULL', 'NULLABLE', 'NULLIF', 'NUMBER',
+    'NUMERIC', 'OBJECT', 'OCTET_LENGTH', 'OF', 'OFF', 'OFFSET',
+    'OIDS', 'OLD', 'ON', 'ONLY', 'OPEN', 'OPERATION', 'OPERATOR',
+    'OPTION', 'OPTIONS', 'OR', 'ORDER', 'ORDINALITY', 'OUT', 'OUTER',
+    'OUTPUT', 'OVERLAPS', 'OVERLAY', 'OVERRIDING', 'OWNER', 'PAD',
+    'PARAMETER', 'PARAMETERS', 'PARAMETER_MODE', 'PARAMETER_NAME',
+    'PARAMETER_ORDINAL_POSITION', 'PARAMETER_SPECIFIC_CATALOG',
+    'PARAMETER_SPECIFIC_NAME', 'PARAMETER_SPECIFIC_SCHEMA', 'PARTIAL',
+    'PASCAL', 'PASSWORD', 'PATH', 'PENDANT', 'PLACING', 'PLI', 'POSITION',
+    'POSTFIX', 'PRECISION', 'PREFIX', 'PREORDER', 'PREPARE', 'PRESERVE',
+    'PRIMARY', 'PRIOR', 'PRIVILEGES', 'PROCEDURAL', 'PROCEDURE', 'PUBLIC',
+    'READ', 'READS', 'REAL', 'RECHECK', 'RECURSIVE', 'REF', 'REFERENCES',
+    'REFERENCING', 'REINDEX', 'RELATIVE', 'RENAME', 'REPEATABLE',
+    'REPLACE', 'RESET', 'RESTART', 'RESTRICT', 'RESULT', 'RETURN',
+    'RETURNED_LENGTH', 'RETURNED_OCTET_LENGTH', 'RETURNED_SQLSTATE',
+    'RETURNS', 'REVOKE', 'RIGHT', 'ROLE', 'ROLLBACK', 'ROLLUP',
+    'ROUTINE', 'ROUTINE_CATALOG', 'ROUTINE_NAME', 'ROUTINE_SCHEMA',
+    'ROW', 'ROWS', 'ROW_COUNT', 'RULE', 'SAVEPOINT', 'SCALE', 'SCHEMA',
+    'SCHEMA_NAME', 'SCOPE', 'SCROLL', 'SEARCH', 'SECOND', 'SECTION',
+    'SECURITY', 'SELECT', 'SELF', 'SENSITIVE', 'SEQUENCE', 'SERIALIZABLE',
+    'SERVER_NAME', 'SESSION', 'SESSION_USER', 'SET', 'SETOF', 'SETS',
+    'SHARE', 'SHOW', 'SIMILAR', 'SIMPLE', 'SIZE', 'SMALLINT', 'SOME',
+    'SOURCE', 'SPACE', 'SPECIFIC', 'SPECIFICTYPE', 'SPECIFIC_NAME', 'SQL',
+    'SQLCODE', 'SQLERROR', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING',
+    'STABLE', 'START', 'STATE', 'STATEMENT', 'STATIC', 'STATISTICS',
+    'STDIN', 'STDOUT', 'STORAGE', 'STRICT', 'STRUCTURE', 'STYLE',
+    'SUBCLASS_ORIGIN', 'SUBLIST', 'SUBSTRING', 'SUM', 'SYMMETRIC',
+    'SYSID', 'SYSTEM', 'SYSTEM_USER', 'TABLE', 'TABLE_NAME', 'TEMP',
+    'TEMPLATE', 'TEMPORARY', 'TERMINATE', 'THAN', 'THEN', 'TIME',
+    'TIMESTAMP', 'TIMEZONE_HOUR', 'TIMEZONE_MINUTE', 'TO', 'TOAST',
+    'TRAILING', 'TRANSACTION', 'TRANSACTIONS_COMMITTED',
+    'TRANSACTIONS_ROLLED_BACK', 'TRANSACTION_ACTIVE', 'TRANSFORM',
+    'TRANSFORMS', 'TRANSLATE', 'TRANSLATION', 'TREAT', 'TRIGGER',
+    'TRIGGER_CATALOG', 'TRIGGER_NAME', 'TRIGGER_SCHEMA', 'TRIM',
+    'TRUE', 'TRUNCATE', 'TRUSTED', 'TYPE', 'UNCOMMITTED', 'UNDER',
+    'UNENCRYPTED', 'UNION', 'UNIQUE', 'UNKNOWN', 'UNLISTEN',
+    'UNNAMED', 'UNNEST', 'UNTIL', 'UPDATE', 'UPPER', 'USAGE',
+    'USER', 'USER_DEFINED_TYPE_CATALOG', 'USER_DEFINED_TYPE_NAME',
+    'USER_DEFINED_TYPE_SCHEMA', 'USING', 'VACUUM', 'VALID', 'VALIDATOR',
+    'VALUE', 'VALUES', 'VARCHAR', 'VARIABLE', 'VARYING', 'VERBOSE',
+    'VERSION', 'VIEW', 'VOLATILE', 'WHEN', 'WHENEVER', 'WHERE', 'WITH',
+    'WITHOUT', 'WORK', 'WRITE', 'YEAR', 'ZONE'
+])
+
+def is_sql_keyword(word):
+    return word.upper() in sql_keywords
+
+def is_pg_keyword(word):
+    return word.upper() in pg_keywords
+
+max_identifier_len = 63
+valid_identifier_re = re.compile('^[a-z][a-z0-9_]*$', re.IGNORECASE)
+
+def valid_identifier(name, label='identifier', reserve=0, strict=True):
+    if not isinstance(name, basestring):
+        raise dbapi.IdentifierError('%s %r must be a string' % (label, name))
+    if len(name) + reserve > max_identifier_len:
+        raise dbapi.IdentifierError('%s %r must be less than %d characters' % 
+                              (label, name, max_identifier_len - reserve))
+    if not valid_identifier_re.match(name):
+        raise dbapi.IdentifierError('%s %r contains invalid characters - use ' 
+            'leading letter, then letters, numbers and _ only' % (label, name))
+    if ((strict and is_sql_keyword(name)) 
+            or (not strict and is_pg_keyword(name))):
+        raise dbapi.IdentifierError('%s %r must not be an SQL reserved word' %
+                              (label, name))
+
+
+_tablecols_re = re.compile('^(\w+)\s*(?:\(([^)]*)\))?$')
+def parse_tablecols(tablecols):
+    match = _tablecols_re.match(tablecols)
+    if not match:
+        raise ValueError('Invalid SQL table(column, ...) specification: %r' %
+                         tablecols)
+    table, columns = match.groups()
+    if columns:
+        columns = [c.strip() for c in columns.split(',')]
+    else:
+        columns = None
+    return table, columns
+
+
+def cursor_cols(curs):
+    """
+    Given a query cursor, return a list of the column names.
+    """
+    return [d[0] for d in curs.description]
diff --git a/cocklebur/dbobj/participation_table.py b/cocklebur/dbobj/participation_table.py
new file mode 100644
index 0000000..f4e7467
--- /dev/null
+++ b/cocklebur/dbobj/participation_table.py
@@ -0,0 +1,242 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.dbobj.result import ResultSet
+from cocklebur.dbobj import table_dict, query_builder
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+class PTSet:
+    """
+    Give the illusion of being a set of slave rows (and maintain PT)
+    """
+    def __init__(self, pt_info, key=None):
+        self.pt_info = pt_info
+        self.set = ResultSet(self.pt_info.table_desc)
+        self.key = key
+        self.initial = set()
+
+    def _add(self, pt_row):
+        """
+        Directly add a pt_row to the set. This differs from the .add() method,
+        which adds a slave column.
+        """
+        slave_key = getattr(pt_row, self.pt_info.slave_col)
+        self.set.append(pt_row)
+        self.pt_info.slave_cache.want(slave_key)
+        self.initial.add(slave_key)
+
+    def __getitem__(self, i):
+        pt_row = self.set[i]
+        slave_key = getattr(pt_row, self.pt_info.slave_col)
+        return self.pt_info.slave_cache[slave_key]
+
+    def __len__(self):
+        return len(self.set)
+
+    def __contains__(self, row):
+        slave_key = getattr(row, self.pt_info.slave_pkey)
+        pt_slave = self.pt_info.slave_col
+        for pt_row in self.set:
+            if getattr(pt_row, pt_slave) == slave_key:
+                return True
+        return False
+
+    def set_key(self, key):
+        # When we're creating a new master row, we don't know it's key until
+        # later.
+        self.key = key
+
+    def slave_keys(self):
+        # This method is primarily for unit testing purposes
+        slave_col = self.pt_info.slave_col
+        return [getattr(pt_row, slave_col) for pt_row in self.set]
+
+    def add(self, row):
+        """
+        Add a *slave* row to the pt (creating pt row if necessary).
+        """
+        if row in self:
+            return
+        self._add_slave_key(getattr(row, self.pt_info.slave_pkey))
+        self.pt_info.slave_cache.add(row)
+
+    def _add_slave_key(self, slave_key):
+        pt_info = self.pt_info
+        pt_row = pt_info.table_desc.get_row()
+        setattr(pt_row, pt_info.slave_col, slave_key)
+        self.set.append(pt_row)
+
+    def add_slave_key(self, slave_key):
+        self._add_slave_key(slave_key)
+        self.pt_info.slave_cache.preload([slave_key])
+
+    def remove(self, row):
+        slave_key = getattr(row, self.pt_info.slave_pkey)
+        pt_slave = self.pt_info.slave_col
+        for i in range(len(self)-1, -1, -1):
+            if getattr(self.set[i], pt_slave) == slave_key:
+                del self.set[i]
+
+    def pop(self, index):
+        pt_row = self.set.pop(index)
+        slave_key = getattr(pt_row, self.pt_info.slave_col)
+        return self.pt_info.slave_cache[slave_key]
+
+    def _swap(self, index_a, index_b):
+        pt_row_a, pt_row_b = self.set[index_a], self.set[index_b]
+        for col_desc in self.pt_info.table_desc.get_columns():
+            if not col_desc.primary_key:
+                a = getattr(pt_row_a, col_desc.name) 
+                b = getattr(pt_row_b, col_desc.name)
+                setattr(pt_row_a, col_desc.name, b) 
+                setattr(pt_row_b, col_desc.name, a) 
+
+    def move_up(self, index):
+        if index > 0:
+            self._swap(index, index - 1)
+
+    def move_down(self, index):
+        if index < len(self.set) - 1:
+            self._swap(index, index + 1)
+
+    def db_update(self):
+        assert self.key is not None
+        for pt_row in self.set:
+            setattr(pt_row, self.pt_info.master_col, self.key)
+        self.set.db_update()
+
+    def db_revert(self):
+        self.set.db_revert()
+
+    def db_has_changed(self):
+        return self.set.db_has_changed()
+
+    def save_state(self):
+        self.set.save_state()
+
+    def comma_list(self, slave_col_name):
+        items = [getattr(r, slave_col_name) for r in self]
+        items.sort()
+        return ', '.join(items)
+
+    def get_slave_cache(self):
+        return self.pt_info.slave_cache
+
+    def changes(self):
+        current = set()
+        for pt_row in self.set:
+            current.add(getattr(pt_row, self.pt_info.slave_col))
+        added = [self.pt_info.slave_cache[k] for k in current - self.initial]
+        removed = [self.pt_info.slave_cache[k] for k in self.initial - current]
+        return added, removed
+
+class PTInfo:
+    def __init__(self, table_desc, master_col, slave_col):
+        self.table_desc = table_desc
+        self.master_col = master_col
+        self.slave_col = slave_col
+        pkey_col_desc, = table_desc.get_primary_cols()
+        self.pkey = pkey_col_desc.name
+
+        target_desc = table_desc.get_column(self.master_col).target_column()
+        self.master_pkey = target_desc.name
+
+        target_desc = table_desc.get_column(self.slave_col).target_column()
+        self.slave_pkey = target_desc.name
+        self.slave_cache = table_dict.TableDict(target_desc.table_desc)
+
+        pt_pkey_desc, = table_desc.get_primary_cols()
+        self.pt_pkey = pt_pkey_desc.name
+
+class ParticipationTable:
+    def __init__(self, table_desc, master_col, slave_col):
+        self.pt_info = PTInfo(table_desc, master_col, slave_col)
+        self.sets = {}
+        self._new_set = None
+
+    def preload(self, keys):
+        keys = [key for key in keys if key is not None and key not in self.sets]
+        if keys:
+            for key in keys:
+                self.sets[key] = PTSet(self.pt_info, key)
+            pt_info = self.pt_info
+            query = query_builder.Query(pt_info.table_desc,
+                                        order_by = pt_info.pkey)
+            query.where_in(pt_info.master_col, keys)
+            for row in query.fetchall():
+                key = getattr(row, pt_info.master_col)
+                self.sets[key]._add(row)
+            for key in keys:
+                self.sets[key].save_state()
+            pt_info.slave_cache.preload()
+                
+    def preload_from_result(self, result):
+        master_pkey = self.pt_info.master_pkey
+        self.preload([getattr(r, master_pkey) for r in result])
+
+    def new_set(self):
+        self._new_set = PTSet(self.pt_info)
+        return self._new_set
+
+    def _check_new_set(self):
+        if self._new_set is not None and self._new_set.key is not None:
+            self.sets[self._new_set.key] = self._new_set
+            self._new_set = None
+
+    def __getitem__(self, key):
+        self._check_new_set()
+        if key is None:
+            return self.new_set()
+        else:
+            return self.sets[key]
+
+    def get_slave_cache(self):
+        return self.pt_info.slave_cache
+
+    def db_has_changed(self):
+        self._check_new_set()
+        for set in self.sets.values():
+            if set.db_has_changed():
+                return True
+        return False
+
+    def db_update(self):
+        self._check_new_set()
+        for set in self.sets.values():
+            set.db_update()
+
+    def db_revert(self):
+        self._new_set = None
+        for set in self.sets.values():
+            set.db_revert()
+
+def ptset(table_desc, master_col, slave_col, key=None, filter=None):
+    pt_info = PTInfo(table_desc, master_col, slave_col)
+    ptset = PTSet(pt_info, key)
+    if key is not None:
+        query = query_builder.Query(pt_info.table_desc,
+                                    order_by = pt_info.pkey)
+        query.where('%s = %%s' % pt_info.master_col, key)
+        if filter is not None:
+            query.where(filter)
+        for row in query.fetchall():
+            ptset._add(row)
+        pt_info.slave_cache.preload()
+    return ptset
diff --git a/cocklebur/dbobj/query_builder.py b/cocklebur/dbobj/query_builder.py
new file mode 100644
index 0000000..094cc9a
--- /dev/null
+++ b/cocklebur/dbobj/query_builder.py
@@ -0,0 +1,393 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.dbobj import dbapi, execute, result, misc
+
+
+def wild(term):
+    if term:
+        return term.replace('*', '%').replace('?', '_')
+
+
+def is_wild(term):
+    return term and ('*' in term or '%' in term or '?' in term or '_' in term)
+
+
+class ExprBuilder:
+    def __init__(self, table_desc, conjunction, negate=False):
+        self.table_desc = table_desc
+        self.conjunction = conjunction.upper()
+        self.negate = negate
+        self.where_exprs = []
+
+    def where(self, expr, *args):
+        self.where_exprs.append(('simple', expr, args))
+
+    def where_in(self, incol, values):
+        args = tuple(values)
+        nargs = len(args)
+        if not nargs:
+            # An empty set matches no rows.
+            expr = 'false'
+        else:
+            if isinstance(incol, basestring):
+                # Simple "colname IN (value, value)" query
+                valuefmt = '%s'
+            else:
+                # Multi-column IN query: "(colA, colB) IN ((valueA, valueB),...)
+                ncols = len(incol)
+                if not ncols:
+                    raise dbapi.ProgrammingError('"IN" query with no columns?')
+                elif ncols == 1:
+                    incol = incol[0]
+                    valuefmt = '%s'
+                else:
+                    incol = '(%s)' % ','.join(incol)
+                    valuefmt = '(%s)' % ','.join(['%s'] * ncols)
+                argsi = iter(args)
+                args = []
+                for value in argsi:
+                    if len(value) != ncols:
+                        raise dbapi.ProgrammingError(
+        'Multi-column "IN" query parameter count not equal to column count')
+                    args.extend(value)
+            expr = '%s IN (%s)' % (incol, ','.join([valuefmt] * nargs))
+        self.where_exprs.append(('simple', expr, args))
+
+    def in_select(self, incol, table, op='IN', **kwargs):
+        in_table_desc = self.table_desc.db.get_table(table)
+        if 'columns' not in kwargs:
+            kwargs['columns'] = [incol]
+        in_query = Query(in_table_desc, **kwargs)
+        self.where_exprs.append(('inselect', in_query, (incol, op)))
+        return in_query
+
+    def sub_expr(self, conjunction=None, negate=False):
+        if not conjunction:
+            if self.conjunction == 'AND':
+                conjunction = 'OR'
+            else:
+                conjunction = 'AND'
+        sub = ExprBuilder(self.table_desc, conjunction, negate)
+        self.where_exprs.append(('subexpr', sub, None))
+        return sub
+
+    def __len__(self):
+        n = 0
+        for exprtype, expr, args in self.where_exprs:
+            if exprtype == 'subexpr':
+                n += len(expr)
+            else:
+                n += 1
+        return n
+
+    def build_expr(self):
+        if self.where_exprs:
+            expression, arguments = [], []
+            for exprtype, expr, args in self.where_exprs:
+                if exprtype == 'subexpr':
+                    if len(expr) == 0:
+                        continue
+                    expr, args = expr.build_expr()
+                elif exprtype == 'inselect':
+                    incol, op = args
+                    expr, args = expr.build_expr()
+                    expr = '%s %s (%s)' % (incol, op, expr)
+                expression.append(expr)
+                arguments.extend(args)
+            expression = '(%s)' % (' %s ' % self.conjunction).join(expression)
+            if self.negate:
+                expression = 'NOT ' + expression
+            return expression, arguments
+        else:
+            # NOOP
+            boolean = self.conjunction == 'AND'
+            if self.negate:
+                boolean = not boolean
+            if boolean:
+                return 'True', []
+            else:
+                return 'False', []
+
+class Query:
+    def __init__(self, table_desc,
+                 conjunction = 'AND', negate=False,
+                 distinct = False, 
+                 for_update = False, for_share = False,
+                 order_by = None, group_by = None, 
+                 limit = None, columns = None):
+        self.table_desc = table_desc
+        self.distinct = distinct
+        self.for_update = for_update
+        self.for_share = for_share
+        self.order_by = order_by
+        self.group_by = group_by
+        self.limit = limit
+        self.columns = columns
+        self.joins = []
+        self.where_expr = ExprBuilder(self.table_desc, conjunction, negate)
+        self.sub_query = None
+        self.set_query = None
+
+    def db(self):
+        return self.table_desc.db
+
+    def where(self, expr, *args):
+        self.where_expr.where(expr, *args)
+        return self             # Allow Query(table).where(...).execute(db)
+
+    def where_in(self, incol, values):
+        self.where_expr.where_in(incol, values)
+        return self             # Allow Query(table).where(...).execute(db)
+        
+    def in_select(self, incol, table, op='IN', **kwargs):
+        return self.where_expr.in_select(incol, table, op, **kwargs)
+
+    def by_primary_keys(self, keys):
+        colnames = [col.name for col in self.table_desc.get_primary_cols()]
+        self.where_in(colnames, keys)
+
+    def sub_expr(self, conjunction=None, negate=False):
+        return self.where_expr.sub_expr(conjunction, negate)
+
+    def sub_select(self, **kwargs):
+        self.sub_query = Query(self.table_desc, **kwargs)
+        return self.sub_query
+
+    def _set_query(self, table, op, **kwargs):
+        if table is None:
+            table_desc = self.table_desc
+        else:
+            table_desc = self.table_desc.db.get_table(table)
+        if 'columns' not in kwargs:
+            kwargs['columns'] = self.columns
+        query = Query(table_desc, **kwargs)
+        self.set_query = op, query
+        return query
+
+    def union_query(self, table=None, **kwargs):
+        return self._set_query(table, 'UNION', **kwargs)
+
+    def intersect_query(self, table=None, **kwargs):
+        return self._set_query(table, 'INTERSECT', **kwargs)
+
+    def except_query(self, table=None, **kwargs):
+        return self._set_query(table, 'EXCEPT', **kwargs)
+
+    def join(self, join_expr, *join_args):
+        self.joins.append((join_expr, join_args))
+
+    def build_expr(self, columns = None):
+        table_name = self.table_desc.name
+        if not columns:
+            columns = self.columns
+        query = ['SELECT']
+        query_args = []
+        if self.distinct:
+            query.append('DISTINCT')
+        if columns:
+            query.append(', '.join(columns))
+        else:
+            query.append('%s.*' % table_name)
+        if self.sub_query:
+            sub_expr, sub_args = self.sub_query.build_expr()
+            query.append('FROM (%s) AS %s' % (sub_expr, table_name))
+            query_args.extend(sub_args)
+        else:
+            query.append('FROM %s' % table_name)
+        for join_expr, join_args in self.joins:
+            query.append(join_expr)
+            query_args.extend(join_args)
+        if self.where_expr:
+            where_expr, where_args = self.where_expr.build_expr()
+            query.append('WHERE %s' % where_expr)
+            query_args.extend(where_args)
+        if self.group_by:
+            query.append('GROUP BY %s' % self.group_by)
+        if self.order_by:
+            if type(self.order_by) in (list, tuple):
+                query.append('ORDER BY %s' % ', '.join(self.order_by))
+            else:
+                query.append('ORDER BY %s' % self.order_by)
+#            elif self.table_desc.order_by:
+#                query.append('ORDER BY ' + ', '.join(self.table_desc.order_by))
+#                if self.table_desc.order_reversed:
+#                    query.append('DESC')
+        if self.for_update:
+            query.append('FOR UPDATE')
+        elif self.for_share:
+            query.append('FOR SHARE')
+        if self.limit is not None:
+            query.append('LIMIT %s' % self.limit)
+        if self.set_query:
+            set_op, set_query = self.set_query
+            query.append(set_op)
+            set_expr, set_args = set_query.build_expr()
+            query.append(set_expr)
+            query_args.extend(set_args)
+        return ' '.join(query), query_args
+
+    def execute(self, curs, columns = None):
+        query_expr, query_args = self.build_expr(columns)
+        execute.execute(curs, query_expr, query_args)
+
+    def fetchkeys(self):
+        table_name = self.table_desc.name
+        pkey_cols = ['%s.%s' % (table_name, col.name) 
+                     for col in self.table_desc.get_primary_cols()]
+        return self.fetchcols(pkey_cols)
+
+    def fetchcols(self, columns):
+        """
+        Execute the query, returning tuples of the requested columns.
+        """
+        curs = self.table_desc.db.cursor()
+        try:
+            if type(columns) in (str, unicode):
+                self.execute(curs, [columns])
+                return [r[0] for r in curs.fetchall()]
+            else:
+                self.execute(curs, columns)
+                return [tuple(r) for r in curs.fetchall()]
+        finally:
+            curs.close()
+
+    def fetchall(self, limit=None):
+        """
+        Execute the query, returning a ResultSet containing ResultRows
+        for all the matching rows.
+        """
+        curs = self.table_desc.db.cursor()
+        try:
+            self.execute(curs)
+            rs = result.ResultSet(self.table_desc)
+            rs.from_cursor(curs, limit)
+        finally:
+            curs.close()
+        return rs
+
+    def yieldall(self, fetchcount=100):
+        """
+        Execute the query, yielding up ResultRow objects.
+
+        Note that we can't mix generator functions and try/finally, so
+        this could potentially leak cursors if the caller aborts early,
+        and something prevents the generator being GCed.
+        """
+        curs = self.table_desc.db.cursor()
+        self.execute(curs)
+        while True:
+            rows = curs.fetchmany(fetchcount)
+            if not rows:
+                break
+            for fetch_row in rows:
+                row = self.table_desc.get_row()
+                row.from_fetch(curs.description, fetch_row)
+                yield row
+        curs.close()
+
+    def fetchdict(self):
+        """
+        Execute the query, returning a list of dicts representing the
+        resulting rows. 
+        
+        Some dbapi adapters do this for you - we duplicate the
+        functionality here for portability.
+        """
+        curs = self.table_desc.db.cursor()
+        try:
+            self.execute(curs)
+            cols = misc.cursor_cols(curs)
+            return [dict(zip(cols, row)) for row in curs.fetchall()]
+        finally:
+            curs.close()
+
+    def fetchone(self):
+        """
+        Execute the query, returning a single ResultRow if a single row
+        results, None if no rows match, or raising IntegrityError if
+        more than one row results.
+        """
+        curs = self.table_desc.db.cursor()
+        try:
+            self.execute(curs)
+            fetch_rows = curs.fetchmany(2)
+            if len(fetch_rows) == 0:
+                return None
+            elif len(fetch_rows) > 1:
+                raise dbapi.IntegrityError('fetchone returned more than one row')
+            row = self.table_desc.get_row()
+            row.from_fetch(curs.description, fetch_rows[0])
+        finally:
+            curs.close()
+        return row
+
+    def fetchall_by_keys(self, keys):
+        if not keys:
+            return []
+        # Restore results to the order specific by the user - PG gets this
+        # right, but do others?
+        keys = [tuple(row_keys) for row_keys in keys]
+        self.by_primary_keys(keys)
+        rows_by_key = {}
+        for row in self.fetchall():
+            rows_by_key[row.get_keys()] = row
+        return [rows_by_key[row_keys] 
+                for row_keys in keys
+                if row_keys in rows_by_key]
+
+    def delete(self):
+        query = ['DELETE FROM %s' % self.table_desc.name]
+        query_args = ()
+        if self.where_expr:
+            where_expr, query_args = self.where_expr.build_expr()
+            query.append('WHERE %s' % where_expr)
+        curs = self.table_desc.db.cursor()
+        try:
+            execute.execute(curs, ' '.join(query), query_args)
+        finally:
+            curs.close()
+
+    def aggregate(self, *op):
+        query_expr, query_args = self.build_expr(op)
+        curs = self.table_desc.db.cursor()
+        try:
+            execute.execute(curs, query_expr, query_args)
+            result = curs.fetchone()
+            if len(result) != len(op):
+                raise dbapi.ProgrammingError('aggregate function returned '
+                                             '%d results, expected %d' % 
+                                                (len(result), len(op)))
+            if len(result) == 1:
+                return result[0]
+            else:
+                return result
+        finally:
+            curs.close()
+
+    def update(self, set, *args):
+        query = ['UPDATE %s SET %s' % (self.table_desc.name, set)]
+        args = list(args)
+        if self.where_expr:
+            where_expr, query_args = self.where_expr.build_expr()
+            query.append('WHERE %s' % where_expr)
+            args.extend(query_args)
+        curs = self.table_desc.db.cursor()
+        try:
+            execute.execute(curs, ' '.join(query), args)
+        finally:
+            curs.close()
diff --git a/cocklebur/dbobj/result.py b/cocklebur/dbobj/result.py
new file mode 100644
index 0000000..9c793c4
--- /dev/null
+++ b/cocklebur/dbobj/result.py
@@ -0,0 +1,480 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard lib
+import copy
+
+# 3rd party
+from mx import DateTime
+
+# Module
+from cocklebur.dbobj import dbapi
+from cocklebur.dbobj.execute import execute
+
+
+class _CmdBuilder:
+    """
+    Build SQL commands bit by bit, with additional help for parameters
+    """
+    def __init__(self):
+        self.cmd = []
+        self.args = []
+
+    def append(self, cmd, *args):
+        self.cmd.append(cmd)
+        self.args.extend(args)
+
+    def append_list_of_names(self, names):
+        self.cmd.append('(' + ','.join(names) + ')')
+
+    def append_list_of_values(self, values):
+        self.cmd.append('(' + ','.join(['%s'] * len(values)) + ')')
+        self.args.extend(values)
+
+    def append_name_value_expr(self, join_str, names, values):
+        self.cmd.append(join_str.join(['%s=%%s' % name for name in names]))
+        self.args.extend(values)
+
+    def execute(self, curs):
+        execute(curs, ' '.join(self.cmd), self.args)
+
+class ColumnValue(object):
+    __slots__ = '_initial_value', '_value', '_saved_values'
+
+    def __init__(self, initial_value=None):
+        self._initial_value = self._value = initial_value
+
+    def __getstate__(self):
+        return self._initial_value, self._value
+
+    def __setstate__(self, state):
+        self._initial_value, self._value = state
+
+    def set_value(self, value):
+        self._value = value
+
+    def value(self):
+        return self._value
+
+    def raw_value(self):
+        return self._value
+
+    def set_initial_value(self, value):
+        self._initial_value = self._value = value
+
+    def initial_value(self):
+        return self._initial_value
+
+    def has_changed(self):
+        try:
+            return self._initial_value != self._value
+        except TypeError:
+            return True
+
+    def revert(self):
+        self._value = self._initial_value
+
+    def rollback(self):
+        self._value, self._initial_value = self._saved_values
+
+    def commit(self):
+        del self._saved_values
+
+    def save(self):
+        self._saved_values = self._value, self._initial_value
+
+
+class ResultRow:
+    def __init__(self, table_desc, seed_values=None):
+        self._table_desc = table_desc
+        self._new = True
+        self._columns = {}
+        for col_desc in self._table_desc.get_columns():
+            col_value = ColumnValue(col_desc.initial_value())
+            self._columns[col_desc.name] = col_value
+        if seed_values:
+            for attr, value in seed_values.iteritems():
+                col_desc, col_value = self._colpair(attr)
+                col_value.set_initial_value(col_desc.from_user(value))
+
+    def reset_initial(self, name, value):
+        col_desc = self._table_desc.get_column(name)
+        self._columns[name].set_initial_value(col_desc.from_user(value))
+
+
+    def __getstate__(self):
+        return self._table_desc.db, self._table_desc.name, \
+               self._new, self._columns
+
+    def __setstate__(self, state):
+        db, name, self._new, self._columns = state
+        self._table_desc = db.get_table(name)
+
+    def table_desc(self):
+        return self._table_desc
+
+    def db(self):
+        return self._table_desc.db
+
+    def _colpair(self, col_name):
+        col_desc = self._table_desc.get_column(col_name)
+        return col_desc, self._columns[col_desc.name]
+
+    def from_fetch(self, fetch_desc, fetch_row):
+        for desc, value in zip(fetch_desc, fetch_row):
+            try:
+                col_desc, col_value = self._colpair(desc[0])
+            except KeyError:
+                pass
+            else:
+                col_value.set_initial_value(col_desc.from_sql(value))
+        self._new = False
+
+    def __getattr__(self, name):
+        try:
+            col_desc, col_value = self._colpair(name)
+        except KeyError:
+            raise AttributeError('table "%s" has no column "%s"' % \
+                                 (self._table_desc.name, name))
+        return col_desc.to_user(col_value.value())
+
+    def __setattr__(self, name, value):
+        if not name.startswith('_'):
+            try:
+                col_desc, col_value = self._colpair(name)
+            except KeyError:
+                pass
+            else:
+                col_value.set_value(col_desc.from_user(value))
+                return
+        self.__dict__[name] = value
+
+    def __repr__(self):
+        cols = ['%s=%s' % (d.name, repr(self._columns[d.name].value()))
+                for d in self._table_desc.get_columns()]
+        return '<%s.%s: %s>' % \
+            (self._table_desc.db.dsn.database, self._table_desc.name, 
+             ', '.join(cols))
+
+    def db_desc(self, changesonly=True):
+        def short(s):
+            s = str(s)
+            if len(s) > 20:
+                return '%s...%s' % (s[:10], s[-8:])
+            return s
+        desc = []
+        count = 0
+        for col_desc in self._table_desc.get_columns():
+            col_value = self._columns[col_desc.name]
+            value = col_value.raw_value()
+            has_changed = ((self._new and value is not None) 
+                           or col_value.has_changed())
+            if has_changed:
+                count += 1
+            if not changesonly or has_changed or col_desc.primary_key:
+                new = short(col_desc.to_user(value))
+                if col_desc.obscure:
+                    desc.append('%s:***' % (col_desc.name))
+                elif col_value.initial_value() is None or not has_changed:
+                    desc.append('%s:%s' % (col_desc.name, new))
+                else:
+                    old = short(col_desc.to_user(col_value.initial_value()))
+                    desc.append('%s:%s->%s' % (col_desc.name, old, new))
+        if not count and (changesonly or self._new):
+            return None
+        return '%s[%s]' % (self._table_desc.name, ', '.join(desc))
+
+    def is_new(self):
+        return self._new
+
+    def db_has_changed(self):
+        """
+        Has the application changed any column values since the db fetch
+        """
+        if 0:
+            import sys
+            for col_desc in self._table_desc.get_columns():
+                col_value = self._columns[col_desc.name]
+                if col_value.has_changed():
+                    print >> sys.stderr, 'changed %s: %r -> %r' %\
+                        (col_desc.name, col_value._initial_value, 
+                         col_value._value)
+        for col_value in self._columns.values():
+            if col_value.has_changed():
+                return True
+        return False
+
+    def db_clone(self):
+        # NOTE - does not clone seed values!
+        clone = ResultRow(self._table_desc)
+        for col_desc in self._table_desc.get_columns():
+            if col_desc not in self._table_desc.primary_keys:
+                value = self._columns[col_desc.name].value()
+                clone._columns[col_desc.name].set_value(value)
+        return clone
+
+    def db_revert(self):
+        for col_value in self._columns.values():
+            col_value.revert()
+
+    def db_rollback(self):
+        self._new = self._saved_new
+        for col_value in self._columns.values():
+            col_value.rollback()
+
+    def db_commit(self):
+        del self._saved_new
+        for col_value in self._columns.values():
+            col_value.commit()
+
+    def db_refetch(self, for_update=False):
+        if not self._new:
+            table = self._table_desc.name
+            curs = self.db().cursor()
+            try:
+                pkey_names, pkey_values = self._get_pkey(initial = False)
+                if not pkey_names:
+                    raise dbapi.ProgrammingError('No primary key defined for "%s"' % table)
+                cmd = _CmdBuilder()
+                cmd.append('SELECT * FROM %s WHERE' % table)
+                cmd.append_name_value_expr(' AND ', pkey_names, pkey_values)
+                if for_update:
+                    cmd.append('FOR UPDATE')
+                cmd.execute(curs)
+                result = curs.fetchmany(2)
+                assert len(result) == 1
+                self.from_fetch(curs.description, result[0])
+            finally:
+                curs.close()
+
+    def _get_pkey(self, initial = True):
+        pkey_names, pkey_values = [], []
+        for col_desc in self._table_desc.primary_keys:
+            pkey_names.append(col_desc.name)
+            if initial:
+                value = self._columns[col_desc.name].initial_value()
+            else:
+                value = self._columns[col_desc.name].value()
+            pkey_values.append(col_desc.to_sql(value))
+        return pkey_names, pkey_values
+
+    def get_keys(self):
+        return tuple(self._get_pkey()[1])
+
+    def get_ref(self, col_name):
+        col_desc, col_value = self._colpair(col_name)
+        ref_col_desc = col_desc.target_column()
+        ref_table_desc = ref_col_desc.table_desc
+        row = ref_table_desc.get_row()
+        curs = self.db().cursor()
+        try:
+            value = col_value.raw_value()
+            if value is not None:
+                execute(curs, 'SELECT * FROM %s WHERE %s = %%s' % \
+                        (ref_table_desc.name, ref_col_desc.name),
+                        (col_desc.to_sql(value),))
+                result = curs.fetchone()
+                if result:
+                    row.from_fetch(curs.description, result)
+        finally:
+            curs.close()
+        return row
+
+    def db_merge(self, src_row):
+        """
+        Merge values from /src_row/ into /self/
+
+        It is not necessary for /src_row/ to refer to the same table - the
+        merging is done by column name and mismatches are ignored. Values 
+        are only copied if they have been changed in /src_row/.
+        """
+        assert isinstance(src_row, ResultRow)
+        for col_desc in self._table_desc.get_columns():
+            src_col_value = src_row._columns.get(col_desc.name)
+            if src_col_value is not None and src_col_value.has_changed():
+                col_value = self._columns[col_desc.name]
+                col_value.set_value(src_col_value.value())
+
+    def db_update(self, refetch=True):
+        changed_cols, new_values = [], []
+        no_change = True
+        for col_desc in self._table_desc.get_columns():
+            col_value = self._columns[col_desc.name]
+            value = col_value.raw_value()
+            changed = ((self._new and value is not None) 
+                       or col_value.has_changed())
+            if changed:
+                no_change = False
+            elif col_desc.auto_timestamp:
+                # Not "changed", but auto-updating with other updates
+                value = DateTime.now()
+                changed = True
+            if changed:
+                changed_cols.append(col_desc.name)
+                new_values.append(col_desc.to_sql(value))
+        if no_change:
+            return
+        table = self._table_desc.name
+        curs = self.db().cursor()
+        try:
+            if self._new:
+                cmd = _CmdBuilder()
+                cmd.append('INSERT INTO %s' % table)
+                cmd.append_list_of_names(changed_cols)
+                cmd.append('VALUES')
+                cmd.append_list_of_values(new_values)
+                cmd.execute(curs)
+                if not refetch:
+                    return
+                execute(curs, 'SELECT * FROM %s WHERE oid=%%s' % table, 
+                        (curs.oidValue,))
+            else:
+                pkey_names, pkey_values = self._get_pkey()
+                if not pkey_names:
+                    raise dbapi.ProgrammingError('No primary key defined for "%s"' % table)
+                cmd = _CmdBuilder()
+                cmd.append('UPDATE %s SET' % table)
+                cmd.append_name_value_expr(', ', changed_cols, new_values)
+                cmd.append('WHERE')
+                cmd.append_name_value_expr(' and ', pkey_names, pkey_values)
+                cmd.execute(curs)
+                if curs.rowcount != 1:
+                    raise dbapi.RecordDeleted('Record has been deleted')
+                pkey_names, pkey_values = self._get_pkey(initial = False)
+                if not refetch:
+                    return
+                cmd = _CmdBuilder()
+                cmd.append('SELECT * FROM %s WHERE' % table)
+                cmd.append_name_value_expr(' and ', pkey_names, pkey_values)
+                cmd.execute(curs)
+            for col_value in self._columns.values():
+                col_value.save()
+            self._saved_new = self._new
+            self.db().add_pending(self)
+            result = curs.fetchmany(2)
+            assert len(result) == 1
+            self.from_fetch(curs.description, result[0])
+        finally:
+            curs.close()
+
+    def db_delete(self):
+        if self._new:
+            return
+        curs = self.db().cursor()
+        try:
+            pkey_names, pkey_values = self._get_pkey()
+            cmd = _CmdBuilder()
+            cmd.append('DELETE FROM %s WHERE' % self._table_desc.name)
+            cmd.append_name_value_expr(' and ', pkey_names, pkey_values)
+            cmd.execute(curs)
+            self.db().del_pending(self)
+        finally:
+            curs.close()
+
+    def db_nextval(self, colname):
+        return self._table_desc.nextval(colname)
+
+
+class ResultSet(list):
+    """
+    Behaves like a list of ResultRow instances, inserts and deletes are
+    reflected in the database table.
+    """
+    # We'd like to demand-load the rows, but there's not much point
+    # because Postgres cursors don't return the count of rows matched,
+    # making the application unlike to abort early (although demand
+    # loading might allow the application to give early feedback to
+    # the user). 
+    def __init__(self, _table_desc):
+        self._table_desc = _table_desc
+        self.save_state()
+        self._rows_deleted = []
+
+    def save_state(self):
+        self._rows_orig = self[:]
+
+    def __getstate__(self):
+        return self._table_desc.db, self._table_desc.name, \
+               self._rows_orig, self._rows_deleted
+
+    def __setstate__(self, state):
+        db, name, self._rows_orig, self._rows_deleted = state
+        self._table_desc = db.get_table(name)
+
+    def table_desc(self):
+        return self._table_desc
+
+    def db(self):
+        return self._table_desc.db
+
+    def from_cursor(self, curs, limit=None):
+        fetch_desc = curs.description
+        while 1:
+            fetch_result = curs.fetchmany(100)
+            if not fetch_result:
+                break
+            for fr in fetch_result:
+                if limit is not None and len(self) >= limit:
+                    raise dbapi.TooManyRecords('Too many records (limit is %d)' % limit)
+                row = self._table_desc.get_row()
+                row.from_fetch(fetch_desc, fr)
+                self.append(row)
+        self.save_state()
+
+    def __delitem__(self, n):
+        self.pop(n)
+
+    def pop(self, n):
+        row = list.pop(self, n)
+        if not row.is_new():
+            self._rows_deleted.append(row)
+        return row
+
+    def remove(self, value):
+        list.remove(self, value)
+        self._rows_deleted.append(value)
+
+    def new_row(self, **seed_values):
+        return self._table_desc.get_row(seed_values=seed_values)
+
+    def db_revert(self):
+        self[:] = self._rows_orig
+        for row in self:
+            row.db_revert()
+        self._rows_deleted = []
+
+    def db_update(self):
+        for row in self._rows_deleted:
+            try:
+                row.db_delete()
+            except dbapi.IntegrityError:
+                self.append(row)
+                self._rows_deleted.remove(row)
+                raise
+        self._rows_deleted = []
+        for row in self:
+            row.db_update()
+        self.save_state()
+
+    def db_has_changed(self):
+        if self._rows_deleted:
+            return True
+        for row in self:
+            if row.db_has_changed():
+                return True
+        return False
diff --git a/cocklebur/dbobj/table_describer.py b/cocklebur/dbobj/table_describer.py
new file mode 100644
index 0000000..8f584f4
--- /dev/null
+++ b/cocklebur/dbobj/table_describer.py
@@ -0,0 +1,205 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+from cocklebur.dbobj.execute import execute
+from cocklebur.dbobj import misc
+from cocklebur.dbobj import result
+from cocklebur.dbobj import column_describer
+from cocklebur.dbobj import table_extras
+
+class IndexDescriber(table_extras.TableExtra):
+    def __init__(self, table_desc, name, table, col_names, 
+                 unique = False, using = None):
+        self.table_desc = table_desc
+        self.name = name
+        self.table = table
+        self.col_names = col_names
+        self.unique = unique
+        self.using = using
+
+    def create_sql(self):
+        if not self.table_desc.db.db_has_relation(self.name):
+            sql = ['CREATE']
+            if self.unique:
+                sql.append('UNIQUE')
+            sql.extend(('INDEX', self.name, 'ON', self.table))
+            if self.using:
+                sql.append(self.using.upper())
+            sql.append('(%s)' % ','.join(self.col_names))
+            return ' '.join(sql)
+
+    def drop_sql(self):
+        return 'DROP INDEX %s' % self.name
+
+
+class TableDescriber:
+    def __init__(self, db, name, row_class = None):
+#        assert db.__class__.__name__ == 'DatabaseDescriber'
+        misc.valid_identifier(name, 'table identifier', strict=False)
+        self.db, self.name = db, name
+        self.row_class = row_class or result.ResultRow
+        self.columns = []
+        self.extras = table_extras.TableExtras()  # Sequences, indecies, etc
+        self.columns_by_name = {}
+        self.primary_keys = []
+        self.order_by = None
+        self.order_reversed = False
+        self.owned_by = None
+
+    valid_name_re = re.compile('^[a-z_]\w*$', re.IGNORECASE)
+
+    def column(self, name, type, **kwargs):
+        misc.valid_identifier(name, 'column identifier', strict=False)
+        name = name.lower()
+        col_desc = type(self, name, **kwargs)
+        if col_desc.primary_key:
+            self.primary_keys.append(col_desc)
+        col_desc.table_desc = self
+        self.columns.append(col_desc)
+        self.columns_by_name[name] = col_desc
+
+    def order_by_cols(self, *names):
+        self.order_by = names
+        self.order_reversed = False
+
+    def reverse_order_by_cols(self, *names):
+        self.order_by = names
+        self.order_reversed = True
+
+    def extra(self, extra):
+        self.extras.append(extra)
+
+    def get_column(self, name):
+        return self.columns_by_name[name.lower()]
+
+    def get_columns(self):
+        return self.columns
+
+    def get_primary_cols(self):
+        return self.primary_keys
+
+    def get_row(self, seed_values=None):
+        return self.row_class(self, seed_values=seed_values)
+
+    def add_index(self, name, col_names, **kwargs):
+#        name = '%s_%s' % (self.name, name)
+        misc.valid_identifier(name, 'index identifier', strict=False)
+        self.extra(IndexDescriber(self, name, self.name, col_names, **kwargs))
+
+    def grant(self, curs, user):
+        objs = [self.name] + [e.name for e in self.extras if e.needs_grant]
+        execute(curs, 
+                'GRANT SELECT, UPDATE, INSERT, DELETE, REFERENCES'
+                ' ON %s TO "%s"' % (', '.join(objs), user))
+
+    def revoke(self, curs, user):
+        objs = [self.name] + [e.name for e in self.extras if e.needs_grant]
+        execute(curs, 
+                'REVOKE SELECT, UPDATE, INSERT, DELETE, REFERENCES '
+                ' ON %s FROM "%s"' % (', '.join(objs), user))
+
+    def owner(self, curs, owner):
+        execute(curs, 'ALTER TABLE %s OWNER TO "%s"' % (self.name, owner))
+        for extra in self.extras:
+            extra.owner(curs, owner)
+        self.owned_by = owner
+
+    def create(self, grant=None, owner=None):
+        cmds = []
+        for extra in self.extras:
+            sql = extra.pre_create_sql()
+            if sql is not None:
+                cmds.append(sql)
+        if not self.db.db_has_relation(self.name):
+            col_sql = []
+            for col in self.columns:
+                col.create_sql(col_sql)
+            if self.primary_keys:
+                pkey_cols = [coldesc.name for coldesc in self.primary_keys]
+                col_sql.append('PRIMARY KEY (%s)' % ', '.join(pkey_cols))
+            table_sql = [
+                'CREATE TABLE %s (' % self.name,
+                ',\n'.join(['    %s' % c for c in col_sql]),
+                ') WITH OIDS',
+                '',
+            ]
+            cmds.append('\n'.join(table_sql))
+        for extra in self.extras:
+            sql = extra.post_create_sql()
+            if sql is not None:
+                cmds.append(sql)
+        curs = self.db.cursor()
+        try:
+            for cmd in cmds:
+                execute(curs, cmd)
+            if cmds and grant:
+                self.grant(curs, grant)
+            if cmds and owner:
+                self.owner(curs, owner)
+        finally:
+            curs.close()
+
+    def rename(self, new_name):
+        misc.valid_identifier(new_name, 'table identifier', strict=False)
+        curs = self.db.cursor()
+        try:
+            execute(curs, 'ALTER TABLE %s RENAME TO %s' % (self.name, new_name))
+            self.name = new_name
+        finally:
+            curs.close()
+
+    def drop(self):
+        if self.db.db_has_relation(self.name):
+            curs = self.db.cursor()
+            try:
+                for extra in self.extras:
+                    sql = extra.pre_drop_sql()
+                    if sql is not None:
+                        execute(curs, sql)
+                execute(curs, 'DROP TABLE %s' % self.name)
+                # PG drops objects related to the table
+                for extra in self.extras:
+                    sql = extra.post_drop_sql()
+                    if sql is not None:
+                        execute(curs, sql)
+            finally:
+                curs.close()
+
+    def dependancies(self):
+        dependancies = set()
+        for col_desc in self.columns:
+            dependancies.update(col_desc.dependancies())
+        dependancies.discard(self.name.lower())
+        return dependancies
+
+    def nextval(self, colname):
+        col_desc = self.get_column(colname)
+        curs = self.db.cursor()
+        try:
+            execute(curs, col_desc.nextval_sql())
+            return curs.fetchone()[0]
+        finally:
+            curs.close()
+
+    def reset_sequences(self, curs):
+        for col_desc in self.columns:
+            col_desc.reset_sequences(curs)
diff --git a/cocklebur/dbobj/table_dict.py b/cocklebur/dbobj/table_dict.py
new file mode 100644
index 0000000..268984b
--- /dev/null
+++ b/cocklebur/dbobj/table_dict.py
@@ -0,0 +1,83 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+from cocklebur.dbobj import query_builder
+
+class TableDict(dict):
+    def __init__(self, table_desc, key_col = None):
+        self.table_desc = table_desc
+        if key_col is None:
+            pkey_cols = self.table_desc.get_primary_cols()
+            assert len(pkey_cols) == 1
+            self.primary_key_name = pkey_cols[0].name
+        else:
+            self.primary_key_name = key_col
+        self.pending = None
+        self.query_obj = None
+
+    def want(self, key):
+        """
+        Record that we want /key/ preloaded
+        """
+        if key not in self:
+            if self.pending is None:
+                self.pending = set()
+            self.pending.add(key)
+
+    def add(self, row):
+        primary_key = getattr(row, self.primary_key_name)
+        self[primary_key] = row
+
+    def from_result(self, result_set):
+        for row in result_set:
+            self.add(row)
+
+    def preload(self, keys=()):
+        keys = set(keys)
+        if self.pending:
+            keys.update(self.pending)
+        keys = keys - set(self)
+        if keys:
+            query = query_builder.Query(self.table_desc)
+            query.where_in(self.primary_key_name, keys)
+            self.from_result(query.fetchall())
+        self.pending = None
+
+    def preload_all(self):
+        query = query_builder.Query(self.table_desc)
+        self.from_result(query.fetchall())
+
+    def option_list(self, label_col):
+        options = [(getattr(r, label_col), k) for k, r in self.items()]
+        options.sort()
+        return [(k, l) for l, k in options]
+
+    def query(self, **kwargs):
+        self.query_obj = query_builder.Query(self.table_desc)
+
+    def where(self, where, *args):
+        self.query_obj.where(where, *args)
+
+    def fetch_preload(self, limit=None):
+        result = self.query_obj.fetchall(limit=limit)
+        self.from_result(result)
+        self.query_obj = None
+        return result
diff --git a/cocklebur/dbobj/table_extras.py b/cocklebur/dbobj/table_extras.py
new file mode 100644
index 0000000..5ff6484
--- /dev/null
+++ b/cocklebur/dbobj/table_extras.py
@@ -0,0 +1,40 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+class TableExtra:
+
+    needs_grant = False
+
+    def pre_create_sql(self):
+        pass
+
+    def post_create_sql(self):
+        pass
+
+    def pre_drop_sql(self):
+        pass
+
+    def post_drop_sql(self):
+        pass
+
+    def owner(self, curs, owner):
+        pass
+
+
+class TableExtras(list):
+    pass
diff --git a/cocklebur/exepath.py b/cocklebur/exepath.py
new file mode 100644
index 0000000..007effe
--- /dev/null
+++ b/cocklebur/exepath.py
@@ -0,0 +1,27 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+
+default = '/usr/bin:/usr/local/bin'
+def exepath(app):
+
+    for path in (os.environ.get('PATH') or default).split(':'):
+        filepath = os.path.join(path, app)
+        if os.access(filepath, os.X_OK):
+            return filepath
diff --git a/cocklebur/filename_safe.py b/cocklebur/filename_safe.py
new file mode 100644
index 0000000..099c65e
--- /dev/null
+++ b/cocklebur/filename_safe.py
@@ -0,0 +1,35 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import re
+
+bad_re = re.compile(r'[^a-zA-Z0-9]+')
+
+def filename_safe(name):
+    """
+    Transform a string to make it safe for use as a filename
+    """
+    fields = []
+    for f in bad_re.split(name):
+        if f.islower():
+            f = f.capitalize()
+        fields.append(f)
+    return ''.join(fields)
+
+if __name__ == '__main__':
+    print filename_safe('IGgLe nog-flib._.pot9')
diff --git a/cocklebur/foreign_key.py b/cocklebur/foreign_key.py
new file mode 100644
index 0000000..635564a
--- /dev/null
+++ b/cocklebur/foreign_key.py
@@ -0,0 +1,85 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from cocklebur.dbobj import table_dict
+
+class ForeignKeySearch:
+    def __init__(self, row, col_name, search_col, foreign_row_cache = None):
+        self.row = row
+        self.col_name = col_name
+        self.search_col = search_col
+        db = self.row.db()
+        col_desc = self.row.table_desc().get_column(col_name)
+        assert isinstance(col_desc, dbobj.ReferenceColumn)
+        self.foreign_table_desc = db.get_table(col_desc.references)
+        pkey_cols = self.foreign_table_desc.get_primary_cols()
+        assert len(pkey_cols) == 1
+        self.pkey_col = pkey_cols[0].name
+        if foreign_row_cache is None:
+            self.foreign_row_cache = table_dict.TableDict(self.foreign_table_desc)
+        else:
+            self.foreign_row_cache = foreign_row_cache
+        value = getattr(self.row, self.col_name)
+        if value is not None:
+            self.foreign_row_cache.preload((value,))
+        self.search_term = ''
+        self.reset()
+
+    def reset(self):
+        self.error_msg = ''
+        self.results = None
+        self.query = None
+
+    def new_query(self):
+        self.query = dbobj.Query(self.foreign_table_desc, 
+                                 order_by = self.search_col)
+        if self.search_term:
+            if type(self.search_col) not in (list, tuple):
+                cols = [self.search_col]
+            else:
+                cols = self.search_col
+            for col in cols:
+                self.query.where('%s ILIKE %%s' % col, 
+                                 dbobj.wild(self.search_term))
+
+    def fetchall(self):
+        try:
+            try:
+                results = self.query.fetchall()
+            except dbobj.DatabaseError, e:
+                self.error_msg = str(e)
+            else:
+                if len(results) == 0:
+                    self.error_msg = 'not found'
+                else:
+                    self.results = results
+                    self.foreign_row_cache.from_result(results)
+        finally:
+            self.query = None
+
+    def search(self):
+        self.query()
+        self.fetchall()
+
+    def select(self, index):
+        foreign_key = getattr(self.results[index], self.pkey_col)
+        setattr(self.row, self.col_name, foreign_key)
+        self.reset()
+
+    def ref_row(self):
+        return self.foreign_row_cache[getattr(self.row, self.col_name)]
diff --git a/cocklebur/form_ui/__init__.py b/cocklebur/form_ui/__init__.py
new file mode 100644
index 0000000..637d74e
--- /dev/null
+++ b/cocklebur/form_ui/__init__.py
@@ -0,0 +1,24 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.form_ui.columns import *
+from cocklebur.form_ui.common import *
+from cocklebur.form_ui.elements import *
+from cocklebur.form_ui.formdata import *
+from cocklebur.form_ui.inputbase import *
+from cocklebur.form_ui.inputs import *
+from cocklebur.form_ui.formlib import *
diff --git a/cocklebur/form_ui/columns.py b/cocklebur/form_ui/columns.py
new file mode 100644
index 0000000..9b0b7ed
--- /dev/null
+++ b/cocklebur/form_ui/columns.py
@@ -0,0 +1,134 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.form_ui import common
+
+class _Column(object):
+    __slots__ = 'input', 'name', 'type', 'kwargs'
+
+    def __init__(self, input, name, type, kwargs):
+        self.input = input
+        self.name = name
+        self.type = type
+        self.kwargs = kwargs
+
+    def __getstate__(self):
+        return self.input, self.name, self.type, self.kwargs
+
+    def __setstate__(self, state):
+        self.input, self.name, self.type, self.kwargs = state
+
+class Columns:
+    """
+    A repository for column definitions
+    """
+    def __init__(self):
+        self.columns = []
+        self.col_dict = {}
+        self.input_by_name = {}
+
+    def __iter__(self):
+        return iter(self.columns)
+
+    def add_column(self, input, col_name, col_type, **col_kwargs):
+        self.input_by_name[input.column.lower()] = input
+        col_name = col_name.lower()
+        if col_name in self.col_dict:
+            raise common.FormDefError('column "%s" is used more than once' %
+                                      (col_name,))
+        column = _Column(input, col_name, col_type, col_kwargs)
+        self.col_dict[col_name] = column
+        self.columns.append(column)
+
+    def find_input(self, name):
+        return self.input_by_name[name.lower()]
+
+    def register_table(self, db, table):
+        table_desc = db.new_table(table)
+        for column in self.columns:
+            table_desc.column(column.name, column.type, **column.kwargs)
+
+class ConditionXlink:
+    def __init__(self, name):
+        self.name = name
+        self.skip = None
+
+    def skiptext(self):
+        if self.skip is None:
+            return None
+        input = self.skip.input
+        cond = input.describe_skip(self.skip)
+        if not cond:
+            return None
+        text = []
+        text.append(cond)
+        text.append(' in question %s' % self.question.label)
+        if len(self.question.inputs) > 1:
+            pre_text = getattr(input, 'pre_text', None)
+            text.append(' (')
+            text.append(input.label or pre_text or input.column.replace('_', ' '))
+            text.append(')')
+        text.append('.')
+        return ''.join(text)
+
+class XlinkHelper:
+    def __init__(self):
+        self.conditions_by_name = {}
+        self.xlinks_pending = {}
+
+    def add_condition(self, skip, question):
+        if skip.name in self.conditions_by_name:
+            raise common.FormDefError('skip name "%s" is used more than once' %
+                                      (skip.name,))
+        self.conditions_by_name[skip.name] = skip, question
+        try:
+            xlinks_pending = self.xlinks_pending.pop(skip.name)
+        except KeyError:
+            pass
+        else:
+            for xlink in xlinks_pending:
+                xlink.skip = skip
+
+    def get_trigger(self, name):
+        xlink = ConditionXlink(name)
+        try:
+            skip, question = self.conditions_by_name[name]
+        except KeyError:
+            self.xlinks_pending.setdefault(name, []).append(xlink)
+        else:
+            xlink.skip = skip
+            xlink.question = question
+        return xlink
+
+
+class FormErrors:
+    def __init__(self):
+        self.by_input = {}
+        self.in_order = []
+
+    def add_error(self, input, e):
+        self.by_input[input.column] = str(e)
+        self.in_order.append('%s: %s' % (input.label or input.column, e))
+
+    def __len__(self):
+        return len(self.by_input)
+
+    def input_has_error(self, input):
+        return input.column in self.by_input
+
+    def input_error(self, input):
+        return self.by_input.get(input.column, '')
diff --git a/cocklebur/form_ui/common.py b/cocklebur/form_ui/common.py
new file mode 100644
index 0000000..055405f
--- /dev/null
+++ b/cocklebur/form_ui/common.py
@@ -0,0 +1,35 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+import inspect
+
+max_identifier_len = 31
+
+
+class FormError(Exception): pass
+
+class FormDefError(FormError): pass
+class ValidationError(FormError): pass
+class FormLibError(FormError): pass
+class FormParseError(FormError): pass
+
+class NoFormError(FormLibError): pass
+class DuplicateFormError(FormLibError): pass
+
+def warn(msg):
+    sys.stderr.write(msg + '\n')
diff --git a/cocklebur/form_ui/elements.py b/cocklebur/form_ui/elements.py
new file mode 100644
index 0000000..e21bc2b
--- /dev/null
+++ b/cocklebur/form_ui/elements.py
@@ -0,0 +1,268 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.form_ui import columns, common, inputs, inputbase, jsmeta
+from cocklebur import datetime
+
+_inputs = None
+def get_inputs():
+    global _inputs
+    if _inputs is None:
+        inps = []
+        for name, attr in vars(inputs).items():
+            try:
+                if issubclass(attr, inputbase.InputBase) \
+                    and name == attr.__name__:
+                    inps.append(attr)
+            except TypeError:
+                pass
+        _inputs = inps
+    return _inputs
+
+class Question(inputbase.FormBase):
+    render = 'Question'
+    disabled = False
+    help = None
+    label = None
+    trigger_mode = 'disable'
+    triggers = []
+    _triggers = []
+    ignoreattrs = 'columns', 'label', '_triggers'
+
+    def __init__(self, text, input=None, inputs=None, 
+                 help=None, disabled=False, trigger_mode=None, triggers=None):
+        self.text = text
+        if help:
+            self.help = help
+        if disabled:
+            self.disabled = disabled
+        if input and inputs:
+            raise common.FormDefError('specify "input" or "inputs", not both')
+        if input:
+            inputs = [input]
+        if inputs is None:
+            inputs = []
+        for input in inputs:
+            if not isinstance(input, inputbase.InputBase):
+                raise common.FormDefError('question inputs must be '
+                                            'InputBase subclasses')
+        self.inputs = inputs
+        if trigger_mode is not None:
+            self.trigger_mode = trigger_mode
+        if triggers is not None:
+            self.triggers = triggers
+        self.columns = []
+
+    def __iter__(self):
+        return iter([])
+
+    def update_labels(self, path=''):
+        self.label = path
+
+    def get_inputs(self):
+        return self.inputs
+
+    def validate(self, namespace, formerrors):
+        for input in self.get_inputs():
+            try:
+                input.validate(namespace)
+            except common.ValidationError, e:
+                formerrors.add_error(input, e)
+
+    def _collect_columns(self, columns):
+        for element in self.get_inputs():
+            element._collect_columns(columns)
+        return columns
+
+    def collect_summary(self, namespace):
+        summary = []
+        for element in self.get_inputs():
+            summary.extend(element.collect_summary(namespace))
+        return summary
+
+    def update_xlinks(self, _helper=None):
+        if _helper is None:
+            _helper = columns.XlinkHelper()
+        for element in self.get_inputs():
+            element.update_xlinks(self, _helper)
+        self._triggers = [_helper.get_trigger(name) for name in self.triggers]
+
+    def skiptext(self):
+        triggerstext = []
+        for condition in self._triggers:
+            condition_text = condition.skiptext()
+            if condition_text:
+                triggerstext.append(condition_text)
+        if triggerstext:
+            if self.trigger_mode == 'enable':
+                triggerstext.insert(0, 'Answer this question if you:')
+            else:
+                triggerstext.insert(0, 'Skip this question if you:')
+        return triggerstext
+
+    def js_meta(self, formerrors, js_meta):
+        js_question = js_meta.question(self.label, self.trigger_mode)
+        for input in self.get_inputs():
+            js_question.add_inputs(input.get_column_names())
+            input.js_question(js_question)
+            if formerrors.input_has_error(input):
+                js_question.has_error = True
+        for predicate in self.triggers:
+            js_question.trigger(predicate)
+
+    def get_defaults(self, defaults=None):
+        if defaults is None:
+            defaults = {}
+        for input in self.get_inputs():
+            input.get_defaults(defaults)
+        return defaults
+
+
+class _ElementContainer(inputbase.FormBase):
+    ignoreattrs = 'columns', 'label'
+
+    def __init__(self, text):
+        self.text = text
+        self.children = []
+        self.label = ''
+
+    def append(self, instance):
+        self.children.append(instance)
+        return instance
+
+    def question(self, text, **kwargs):
+        self.append(Question(text, **kwargs))
+
+    def update_labels(self, path=''):
+        self.label = path
+        for i, child in enumerate(self.children):
+            label = '%s' % (i + 1)
+            if self.label:
+                label = '%s.%s' % (self.label, label)
+            child.update_labels(label)
+
+    def __getitem__(self, i):
+        element = self.children[i]
+        element.label = '%s%s.' % (self.label, i + 1)
+        return element
+
+    def __len__(self):
+        return len(self.children)
+
+    def validate(self, namespace, formerrors=None):
+        if formerrors is None:
+            formerrors = columns.FormErrors()
+        for element in self:
+            element.validate(namespace, formerrors)
+        return formerrors
+
+    def _collect_columns(self, columns):
+        for element in self:
+            element._collect_columns(columns)
+        return columns
+
+    def update_columns(self):
+        self.columns = columns.Columns()
+        self._collect_columns(self.columns)
+
+    def update_xlinks(self, _helper=None):
+        if _helper is None:
+            _helper = columns.XlinkHelper()
+        for element in self:
+            element.update_xlinks(_helper)
+
+    def collect_summary(self, namespace):
+        summary = []
+        for element in self:
+            summary.extend(element.collect_summary(namespace))
+        return summary
+
+    def js_meta(self, formerrors, js_meta=None):
+        if js_meta is None:
+            js_meta = jsmeta.JSMeta()
+        for element in self:
+            element.js_meta(formerrors, js_meta)
+        return js_meta
+
+    def get_inputs(self):
+        inputs = []
+        for element in self:
+            inputs.extend(element.get_inputs())
+        return inputs
+
+    def get_defaults(self, defaults=None):
+        if defaults is None:
+            defaults = {}
+        for element in self:
+            element.get_defaults(defaults)
+        return defaults
+
+
+class SubSection(_ElementContainer):
+    render = 'SubSection'
+
+class Section(_ElementContainer):
+    render = 'Section'
+
+class Form(_ElementContainer):
+    render = 'Form'
+    name = None
+    version = None
+    table = None
+    form_type = 'case'
+    allow_multiple = False
+    update_time = None
+    author = None
+    username = None
+    ignoreattrs = _ElementContainer.ignoreattrs + (
+        'name', 'table', 'version', 'update_time',
+    )
+
+    def __init__(self, text, table=None, name=None,
+                 form_type=None, allow_multiple=False,
+                 update_time=None, author=None, username=None):
+        _ElementContainer.__init__(self, text)
+        if table:
+            self.table = table
+        if name:
+            self.name = name
+        if form_type:
+            self.form_type = form_type
+        if allow_multiple:
+            self.allow_multiple = allow_multiple
+        if update_time:
+            if isinstance(update_time, basestring):
+                try:
+                    update_time = datetime.mx_parse_datetime(update_time)
+                except datetime.Error, e:
+                    raise common.FormDefError('update_time: %s' % e)
+            self.update_time = update_time
+        if author:
+            self.author = author
+        if username:
+            self.username = username
+
+    def extra_column(self, *args, **kwargs):
+        # Legacy support for loading old form definitions
+        pass
+
+class PagedForm(Form):
+    render = 'PagedForm'
+
+    def get_toc(self):
+        return [(i, "%s %s" % (self[i].label, self[i].text)) 
+                for i in range(len(self))]
diff --git a/cocklebur/form_ui/formdata.py b/cocklebur/form_ui/formdata.py
new file mode 100644
index 0000000..a39be02
--- /dev/null
+++ b/cocklebur/form_ui/formdata.py
@@ -0,0 +1,36 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import checkdigit
+
+
+def form_id(summary_id):
+    if summary_id:
+        return 'F' + checkdigit.add_checkdigit(summary_id)
+
+
+def load_form_data(db, form, summary_id):
+    instance_row = None
+    if summary_id is not None:
+        query = db.query(form.table)
+        query.where('summary_id = %s', summary_id)
+        instance_row = query.fetchone() 
+    if instance_row is None:
+        defaults = form.get_defaults()
+        instance_row = db.new_row(form.table, **defaults) 
+    return instance_row
diff --git a/cocklebur/form_ui/formlib.py b/cocklebur/form_ui/formlib.py
new file mode 100644
index 0000000..e76bcd3
--- /dev/null
+++ b/cocklebur/form_ui/formlib.py
@@ -0,0 +1,332 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+#   Describe a library of forms
+
+__all__ = 'FormLibPyFiles', 'FormLibXMLDB', 
+
+import os
+import re
+import fcntl
+from cStringIO import StringIO
+from cocklebur import dbobj
+from cocklebur.form_ui.common import *
+from cocklebur.form_ui.xmlsave import xmlsave
+from cocklebur.form_ui.xmlload import xmlload
+from cocklebur.form_ui.pysave import pysave
+from cocklebur.form_ui.pyload import pyload
+
+class nextversion: pass
+
+class _FormLibForm:
+    def __init__(self, formlib, name, version, **kw):
+        self.__dict__.update(kw)
+        self.formlib = formlib
+        self.name = name
+        self.version = version
+
+    def load(self):
+        return self.formlib.load(self.name, self.version)
+
+    def __repr__(self):
+        return '<form %s vers %s>' % (self.name, self.version)
+
+    def __cmp__(self, other):
+        return cmp((self.name, self.version), (other.name, other.version))
+
+
+class FormLibBase:
+    def __init__(self):
+        self.cache = {}
+
+    def __iter__(self):
+        return []
+
+    def __len__(self):
+        return 0
+
+    table_re = re.compile(r'^form_(.*)_(\d{5})$')
+
+    def form_tables(self, db, table_name):
+        for table in db.get_tables():
+            match = self.table_re.match(table.name)
+            if match and match.group(1) == table_name:
+                yield table
+
+    def tablename(self, name, version):
+        if version is None:
+            version = 0
+        return 'form_%s_%05d' % (name, version)
+
+    def latest(self):
+        latest = {}
+        for ff in self:
+            try:
+                prev_ff = latest[ff.name]
+            except KeyError:
+                latest[ff.name] = ff
+            else:
+                if prev_ff.version < ff.version:
+                    latest[ff.name] = ff
+        latest = latest.values()
+        latest.sort()
+        return latest
+
+    def latest_version(self, name):
+        pass
+
+    def versions(self, name):
+        return []
+
+    def save(self, form, name, version=nextversion):
+        pass
+
+    def load(self, name, version):
+        try:
+            form = self.cache[(name, version)]
+        except KeyError:
+            form = self.cache[(name, version)] = self._load(name, version)
+        return form
+
+    def rename(self, oldname, newname):
+        pass
+
+    def delete(self, name):
+        pass
+
+    def _update(self, form, name, version):
+        form.name = name
+        form.version = version
+        form.table = self.tablename(name, version)
+        form.update_columns()
+        form.update_labels()
+        form.update_xlinks()
+
+
+class FormLibPyFiles(FormLibBase):
+    """
+    A library of forms stored as .py files in a filesystem directory
+    """
+    form_mod_re = re.compile('^([a-zA-Z0-9_]+?)(_[0-9]+)?(\.[^.]*)$')
+
+    def __init__(self, path):
+        FormLibBase.__init__(self)
+        self.path = path
+        self.lock_fd = None
+        self.lockfilename = os.path.join(self.path, '.lock')
+
+    def __iter__(self):
+        files = os.listdir(self.path)
+        files.sort()
+        for fn in files:
+            match = self.form_mod_re.match(fn)
+            if match:
+                name, rev, ext = match.groups()
+                if ext != '.py':
+                    continue
+                if rev:
+                    rev = int(rev[1:])
+                yield _FormLibForm(self, name, rev, 
+                                   filename=os.path.join(self.path, fn))
+
+    def __len__(self):
+        return len(list(iter(self)))
+
+    def _lock(self):
+        self.lock_fd = os.open(self.lockfilename, os.O_WRONLY|os.O_CREAT, 0666)
+        fcntl.lockf(self.lock_fd, fcntl.LOCK_EX)
+
+    def _unlock(self):
+        if self.lock_fd is not None:
+            os.close(self.lock_fd)
+            self.lock_fd = None
+
+    def filename(self, name, version=None, ext='.py'):
+        if version:
+            fn = '%s_%05d%s' % (name, version, ext)
+        else:
+            fn = '%s%s' % (name, ext)
+        return os.path.join(self.path, fn)
+
+    def latest_version(self, name):
+        version = 0
+        for ff in self:
+            if ff.name == name and ff.version > version:
+                version = ff.version
+        return version
+
+    def versions(self, name):
+        return [ff.version for ff in self if ff.name == name]
+
+    def save(self, form, name, version=nextversion):
+        self._lock()
+        try:
+            if version is nextversion:
+                version = self.latest_version(name) + 1
+            filename = self.filename(name, version)
+            f = open(filename, 'w')
+            try:
+                pysave(f, form)
+            except:
+                f.close()
+                os.unlink(filename)
+                raise
+            f.close()
+            self._update(form, name, version)
+            return version
+        finally:
+            self._unlock()
+
+    def _load(self, name, version):
+        try:
+            f = open(self.filename(name, version))
+        except IOError, e:
+            raise NoFormError(str(e))
+        try:
+            form = pyload(f)
+        finally:
+            f.close()
+        self._update(form, name, version)
+        return form
+
+    def rename(self, oldname, newname):
+        self._lock()
+        try:
+            if self.versions(newname):
+                raise DuplicateFormError('form name %r is already used' % 
+                                         newname)
+            count = 0
+            for ff in self:
+                if ff.name == oldname:
+                    os.rename(ff.filename, self.filename(newname, ff.version))
+                    count += 1
+
+            if not count:
+                raise NoFormError('No form %r' % (oldname))
+        finally:
+            self._unlock()
+
+    def delete(self, name):
+        # FUTURE work - some sort of "trashcan" undelete?
+        self._lock()
+        try:
+            count = 0
+            for ff in self:
+                if ff.name == name:
+                    count += 1
+                    try:
+                        os.unlink(ff.filename)
+                    except OSError:
+                        pass
+            if not count:
+                raise NoFormError('No form %r' % (name))
+        finally:
+            self._unlock()
+
+
+
+class FormLibXMLDB(FormLibBase):
+    """
+    A library of forms stored as XML in a database table 
+    (with optional filesystem caching?).
+    """
+    # Implement filesystem caching of parsed forms
+    # Implement in-core caching of names and versions? Only really of use to
+    # admin form edit.
+
+    def __init__(self, db, table):
+        FormLibBase.__init__(self)
+        self.db = db
+        self.table = table
+
+    def __iter__(self):
+        query = self.db.query(self.table, order_by=('name', 'version'))
+        for name, version in query.fetchcols(('name', 'version')):
+            yield _FormLibForm(self, name, version)
+
+    def __len__(self):
+        query = self.db.query(self.table)
+        return int(query.aggregate('COUNT(*)'))
+
+    def latest_version(self, name):
+        query = self.db.query(self.table)
+        query.where('name = %s', name)
+        version = query.aggregate('max(version)')
+        if version:
+            return version
+        return 0
+
+    def versions(self, name):
+        query = self.db.query(self.table, order_by='version')
+        query.where('name = %s', name)
+        return query.fetchcols('version')
+
+    def save(self, form, name, version=nextversion):
+        f = StringIO()
+        xmlsave(f, form)
+        row = self.db.new_row(self.table)
+        row.name = name
+        row.xmldef = f.getvalue()
+        self.db.lock_table(self.table, 'EXCLUSIVE')
+        if version is nextversion:
+            version = self.latest_version(name) + 1
+        else:
+            if version is None:
+                version = 0
+        row.version = version
+        row.db_update()
+        self._update(form, name, version)
+        return version
+
+    def _load(self, name, version):
+        if version is None:
+            version = 0
+        query = self.db.query(self.table)
+        query.where('name = %s', name)
+        query.where('version = %s', version)
+        row = query.fetchone()
+        if row is None:
+            raise NoFormError('No form %r, version %d' % (name, version))
+        form = xmlload(StringIO(row.xmldef))
+        self._update(form, name, version)
+        return form
+
+    def rename(self, oldname, newname):
+        self.db.lock_table(self.table, 'EXCLUSIVE')
+        if self.versions(newname):
+            raise DuplicateFormError('form name %r is already used' % newname)
+        curs = self.db.cursor()
+        try:
+            dbobj.execute(curs, 
+                          'UPDATE %s SET name=%%s WHERE name=%%s' % self.table,
+                          (newname, oldname))
+            if curs.rowcount == 0:
+                raise NoFormError('No form %r' % oldname)
+        finally:
+            curs.close()
+
+    def delete(self, name):
+        self.db.lock_table(self.table, 'EXCLUSIVE')
+        curs = self.db.cursor()
+        try:
+            dbobj.execute(curs, 
+                          'DELETE FROM %s WHERE name=%%s' % self.table, (name,)) 
+            if curs.rowcount == 0:
+                raise NoFormError('No form %r' % name)
+        finally:
+            curs.close()
diff --git a/cocklebur/form_ui/inputbase.py b/cocklebur/form_ui/inputbase.py
new file mode 100644
index 0000000..ac5b18a
--- /dev/null
+++ b/cocklebur/form_ui/inputbase.py
@@ -0,0 +1,295 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import dbobj
+from cocklebur import utils
+from cocklebur.form_ui import common, columns
+
+class FormBase:
+    ignoreattrs = ()
+
+    def __eq__(self, other):
+        # Ugly, but it should only be used for testing purposes.
+        if self.__class__ is not other.__class__:
+            return False
+        a=dict(vars(self)) 
+        b=dict(vars(other))
+        for attr in self.ignoreattrs:
+            a.pop(attr, None)
+            b.pop(attr, None)
+        return a == b
+
+    def __ne__(a, b):
+        return not a == b
+
+    def __repr__(self):
+        attrs = vars(self).keys()
+        attrs.sort()
+        attrs = ['%s=%r' % (a, getattr(self, a)) 
+                 for a in attrs if a not in self.ignoreattrs]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(attrs))
+
+
+class Skip(FormBase):
+    """
+    An "input" can have one or more "skips" defined. The skip has a name,
+    and a set of conditions under which triggers. When triggered, the
+    skip may disable subsequent inputs in the current question, and/or
+    be referred to by later questions (via a trigger) which results in
+    all inputs in that question being skipped or enabled.
+    """
+    not_selected = False
+    show_msg = True
+    skip_remaining = True
+    ignoreattrs = 'input', 'enable_targets', 'disable_targets'
+
+    def __init__(self, name, values, not_selected=None, show_msg=None,
+                 skip_remaining=None):
+        self.name = name
+        self.values = values
+        if not_selected is not None and not_selected != self.not_selected:
+            self.not_selected = not_selected
+        if show_msg is not None and show_msg != self.show_msg:
+            self.show_msg = show_msg
+        if skip_remaining is not None and skip_remaining != self.skip_remaining:
+            self.skip_remaining = skip_remaining
+        self.input = None
+
+class InputBase(FormBase):
+    render = 'TextInput'
+    label = None
+    required = False
+    summarise = False
+    default = None
+    pre_text = None
+    post_text = None
+    locked_column = None        # If set, specifies an overriding column name
+    input_group = None          # Specifies UI grouping
+    skips = []
+    default_options = [
+        ('None', 'None'),
+        ('value', 'Value'),
+    ]
+
+    def __init__(self, column, **kwargs):
+        if len(column) > common.max_identifier_len:
+            raise common.FormDefError('%s input column name %r too long (%d, max %d)' %\
+                        (self.__class__.__name__, column, 
+                         len(column), common.max_identifier_len))
+        if self.locked_column:
+            column = self.locked_column
+        self.column = column
+        if 'label' not in kwargs and 'summary' in kwargs:
+            kwargs['label'] = kwargs.pop('summary')
+        self.__dict__.update(kwargs)
+        for skip in self.skips:
+            if not isinstance(skip, Skip):
+                raise common.FormDefError('%s input %r, skips must be a Skip instance, not %r' % (self.__class__.__name__, self.column, skip))
+            skip.input = self
+
+    def get_column_name(self):
+        return self.column.lower()
+
+    def get_column_names(self):
+        return [self.get_column_name()]
+
+    def get_value(self, ns):
+        return getattr(ns, self.get_column_name(), None)
+
+    def set_value(self, ns, value):
+        setattr(ns, self.get_column_name(), value)
+
+    def get_default(self):
+        return self.default
+
+    def validate(self, ns):
+        value = self.get_value(ns)
+        if not value and self.required:
+            raise common.ValidationError('this field must be answered')
+        return value
+
+    def nscopy(self, src, dst):
+        for name in self.get_column_names():
+            setattr(dst, name, getattr(src, name, None))
+
+    def get_defaults(self, defaults):
+        value = self.get_default()
+        if value is not None:
+            defaults[self.get_column_name()] = value
+
+    def _collect_columns(self, columns):
+        columns.add_column(self, self.column, self.dbobj_type)
+
+    def update_xlinks(self, question, _helper=None):
+        if _helper is None:
+            _helper = columns.XlinkHelper()
+        for skip in self.skips:
+            _helper.add_condition(skip, question)
+
+    def outtrans(self, ns):
+        try:
+            value = self.get_value(ns)
+        except common.ValidationError:
+            value = '*ERR*'
+        if value is not None:
+            return str(value)
+
+    def collect_summary(self, ns):
+        if self.summarise:
+            label = self.label or self.column
+            value = self.outtrans(ns)
+            if value is None:
+                value = 'n/a'
+            if '*' not in label:
+                label = '*%s*' % label
+            return ['%s: %s' % (label, value)]
+        else:
+            return []
+
+    def js_question(self, js_question):
+        pass
+
+    def format(self):
+        return None
+
+    def get_pre_text(self):
+        return self.pre_text
+
+    def get_post_text(self):
+        return self.post_text
+
+
+class TextInputBase(InputBase):
+    dbobj_type = dbobj.StringColumn
+    maxsize = None
+    input_group = 'Text'
+
+    def validate(self, ns):
+        value = InputBase.validate(self, ns)
+        if value is not None:
+            if self.maxsize and len(value) > self.maxsize:
+                raise common.ValidationError('field must be %d characters or less' % self.maxsize)
+        return value
+
+    def _collect_columns(self, columns):
+        columns.add_column(self, self.column, self.dbobj_type, 
+                           size=self.maxsize)
+
+
+class NumberInputBase(InputBase):
+    minimum = None
+    maximum = None
+    input_group = 'Numeric'
+
+    def _checkrange(self, value):
+        if value is not None:
+            if self.minimum is not None and value < self.minimum:
+                raise common.ValidationError('must greater than or equal to %s' % self.minimum)
+            if self.maximum is not None and value > self.maximum:
+                raise common.ValidationError('must less than or equal to %s' % self.maximum)
+
+
+class ChoicesBase(InputBase):
+    direction = 'auto'
+    input_group = 'Discrete'
+
+    def __init__(self, column, **kwargs):
+        # Legacy
+        if 'horizontal' in kwargs:
+            if kwargs['horizontal']:
+                kwargs['direction'] = 'horizontal'
+            else:
+                kwargs['direction'] = 'vertical'
+        if getattr(self, 'choices', None) is None: 
+            self.choices = []
+        InputBase.__init__(self, column, **kwargs)
+
+    def get_choices(self):
+        return self.choices
+# Albatross is not idempotent in it's handling of None when used with al-input
+# or al-select. This poses unresolvable problems here. Give up for now.
+#        for value, label in self.choices:
+#            if str(value) == 'None':
+#                value = ''
+#            choices.append((value, label))
+#        return choices
+
+    def describe_skip(self, skip):
+        if not skip.show_msg:
+            return
+        labels = []
+        for value, label in self.choices:
+            if value in skip.values:
+                labels.append(label)
+        if labels:
+            op = 'selected'
+            if skip.not_selected:
+                op = 'did not select'
+            return '%s %s' % (op, utils.commalist(labels))
+
+    def skiptext(self):
+        skipstext = []
+        for skip in self.skips:
+            if skip.skip_remaining:
+                skiptext = self.describe_skip(skip)
+                if skiptext:
+                    skipstext.append('If you %s, skip the remaining parts of '
+                                     'this question' % (skiptext))
+        return skipstext
+
+    def js_question(self, js_question):
+        for skip in self.skips:
+            js_question.input_skip(skip.name, self.get_column_name(), 
+                                   skip.values, skip.not_selected, 
+                                   skip.skip_remaining)
+
+    def render_horizontal(self):
+        if self.direction == 'auto':
+            fieldlens = [len(c[1]) + 2 
+                         for c in self.choices
+                         if c[1]]
+            return sum(fieldlens) < 45;
+        else:
+            return self.direction == 'horizontal'
+
+class OneChoiceBase(ChoicesBase):
+    maxsize = None
+    dbobj_type = dbobj.StringColumn
+
+    def __init__(self, column, **kwargs):
+        ChoicesBase.__init__(self, column, **kwargs)
+        if self.maxsize:
+            for value, label in self.choices:
+                if len(value) > self.maxsize:
+                    raise common.FormDefError('Length of %r choice exceeds maximum field size (%s)' % (value, self.maxsize))
+
+    def _collect_columns(self, columns):
+        columns.add_column(self, self.column, self.dbobj_type, 
+                           size=self.maxsize)
+
+    def outtrans(self, ns):
+        value = ChoicesBase.outtrans(self, ns)
+        for choice_value, label in self.choices:
+            if choice_value == value:
+                return label
+        return value
diff --git a/cocklebur/form_ui/inputs/__init__.py b/cocklebur/form_ui/inputs/__init__.py
new file mode 100644
index 0000000..c2b33ab
--- /dev/null
+++ b/cocklebur/form_ui/inputs/__init__.py
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# From inputs subdir, load all python (input) modules
+import os, imp
+
+__all__ = []
+
+input_dir = os.path.dirname(__file__)
+for filename in os.listdir(input_dir):
+    if not filename.startswith('_') and filename.endswith('.py'):
+        modname = filename[:-3]
+        file, path, desc = imp.find_module(modname, [input_dir])
+        try:
+            module = imp.load_module(__name__ + '.' + modname, file, path, desc)
+        finally:
+            file.close()
+        ns = globals()
+        for name, sym in vars(module).items():
+            if hasattr(sym, 'type_name'):
+                ns[name] = sym
+                __all__.append(name)
diff --git a/cocklebur/form_ui/inputs/core.py b/cocklebur/form_ui/inputs/core.py
new file mode 100644
index 0000000..c9cc9d4
--- /dev/null
+++ b/cocklebur/form_ui/inputs/core.py
@@ -0,0 +1,139 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    set
+except NameError:
+    from sets import Set as set
+from cocklebur import dbobj
+from cocklebur.form_ui import common, inputbase
+
+class TextInput(inputbase.TextInputBase):
+    type_name = 'Text Input'
+    maxsize = 60
+
+
+class IntInput(inputbase.NumberInputBase):
+    type_name = 'Integer Input'
+    dbobj_type = dbobj.IntColumn
+
+    def validate(self, ns):
+        value = inputbase.NumberInputBase.validate(self, ns)
+        if isinstance(value, basestring):
+            try:
+                if not value.strip():
+                    return None
+                value = float(value)                            # Accept float
+                int_value = int(round(value))
+                if int_value != value:
+                    value = int_value
+                    self.set_value(ns, value)                   # But truncate
+            except ValueError:
+                raise common.ValidationError('value must be a number')
+        self._checkrange(value)
+        return value
+
+
+class FloatInput(inputbase.NumberInputBase):
+    type_name = 'Decimal Input'
+    dbobj_type = dbobj.FloatColumn
+
+    def validate(self, ns):
+        value = inputbase.NumberInputBase.validate(self, ns)
+        if isinstance(value, basestring):
+            if not value.strip():
+                return None
+            try:
+                value = float(value)
+            except ValueError:
+                raise common.ValidationError('value must be a number')
+        self._checkrange(value)
+        return value
+
+
+class TextArea(inputbase.TextInputBase):
+    type_name = 'Text Area Input'
+    render = 'TextArea'
+    maxsize = 300
+
+
+class DropList(inputbase.OneChoiceBase):
+    type_name = 'Drop List Input'
+    render = 'DropList'
+
+class RadioList(inputbase.OneChoiceBase):
+    type_name = 'Radio Button List Input'
+    render = 'RadioList'
+
+class YesNo(RadioList):
+    type_name = 'Yes/No Input'
+    choices = [
+        ('True', 'Yes'), 
+        ('False', 'No'),
+        ('Unknown', 'Unknown'),
+        ('None', 'Not answered'),
+    ]
+
+class CheckBoxes(inputbase.ChoicesBase):
+    type_name = 'Checkboxes Input'
+    render = 'CheckBoxes'
+    dbobj_type = dbobj.BooleanColumn
+
+    def __init__(self, column, **kwargs):
+        inputbase.ChoicesBase.__init__(self, column, **kwargs)
+        for value, label in self.choices:
+            if (len(self.column) + len(value)) > common.max_identifier_len:
+                raise common.FormDefError('column %r choice %r combined are too long (max %d)' % (self.column, value, common.max_identifier_len))
+
+    def get_column_names(self):
+        return [(self.column + name).lower() for name, label in self.choices]
+
+    def js_question(self, js_question):
+        for skip in self.skips:
+            inputs = [(self.column + name).lower() for name in skip.values]
+            js_question.inputs_skip(skip.name, inputs, 
+                                    skip.not_selected, skip.skip_remaining)
+
+    def _collect_columns(self, columns):
+        for col_name, col_text in self.choices:
+            columns.add_column(self, (self.column + col_name).lower(), 
+                               self.dbobj_type, default='False')
+
+    def get_value(self, ns):
+        values = []
+        for col_name, col_text in self.choices:
+            value = getattr(ns, (self.column + col_name).lower(), None)
+            if value:
+                values.append(col_name)
+        return values
+
+    def get_defaults(self, defaults):
+        values = set([value.lower() for value, label in self.choices])
+        if self.default:
+            for value in self.default.split(','):
+                value = value.strip().lower()
+                if value in values:
+                    defaults[self.column + value] = True
+
+    def outtrans(self, ns):
+        values = self.get_value(ns)
+        if values:
+            result = []
+            for value, label in self.choices:
+                if value in values:
+                    result.append(label)
+            return '/'.join(result)
diff --git a/cocklebur/form_ui/inputs/datetime.py b/cocklebur/form_ui/inputs/datetime.py
new file mode 100644
index 0000000..e9c440b
--- /dev/null
+++ b/cocklebur/form_ui/inputs/datetime.py
@@ -0,0 +1,86 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, datetime
+from cocklebur.form_ui import common
+from cocklebur.form_ui.inputbase import InputBase
+
+
+class _DateTimeInputBase(InputBase):
+    default_options = InputBase.default_options + [
+        ('now', 'Current Time/Date'),
+    ]
+    input_group = 'Date & time'
+
+    def __init__(self, columns, **kwargs):
+        InputBase.__init__(self, columns, **kwargs)
+
+    def validate(self, ns):
+        value = InputBase.validate(self, ns)
+        if not value:
+            return None
+        try:
+            return self.parser(value)
+        except datetime.Error, e:
+            raise common.ValidationError(str(e))
+
+    def get_default(self):
+        if self.default == 'now':
+            return datetime.now()
+        else:
+            return self.default
+
+    def get_post_text(self):
+        if self.post_text:
+            return self.post_text
+        else:
+            return self.parser.help
+
+    def format(self):
+        return self.parser.format
+
+    def outtrans(self, ns):
+        try:
+            value = self.validate(ns)
+        except common.ValidationError:
+            return '*ERR*'
+        if value is not None:
+            return value.strftime(self.format())
+
+
+class DateInput(_DateTimeInputBase):
+    type_name = 'Date Input'
+    render = 'DateInput'
+    dbobj_type = dbobj.DateColumn
+    parser = datetime.mx_parse_date
+
+
+class TimeInput(_DateTimeInputBase):
+    type_name = 'Time Input'
+    dbobj_type = dbobj.TimeColumn
+    parser = datetime.mx_parse_time
+
+
+class DatetimeInput(_DateTimeInputBase):
+    type_name = 'Date/Time Input'
+    render = 'DateInput'
+    dbobj_type = dbobj.DatetimeColumn
+    parser = datetime.mx_parse_datetime
+
+class FormDateInput(DatetimeInput):
+    type_name = 'Primary Form Date'
+    locked_column = 'form_date'
diff --git a/cocklebur/form_ui/inputs/health.py b/cocklebur/form_ui/inputs/health.py
new file mode 100644
index 0000000..c0ae42a
--- /dev/null
+++ b/cocklebur/form_ui/inputs/health.py
@@ -0,0 +1,57 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+import core
+
+class AgeInput(core.IntInput):
+    type_name = 'Age Input'
+    post_text = '(years)'
+    minimum = 0
+    maximum = 140
+
+class BodyTemperatureInput(core.FloatInput):
+    type_name = 'Body Temperature Input (20-50 degree C)'
+    post_text = 'Degrees C'
+    minimum = 20
+    maximum = 50
+
+TemperatureInput = BodyTemperatureInput         # Backward compat
+
+class LabTestResult(core.RadioList):
+    type_name = 'Lab Test Results'
+    choices = [
+        ('NotPerformed', 'Not performed'),
+        ('Positive', 'Positive'),
+        ('Negative', 'Negative'),
+        ('Pending', 'Pending'),
+        ('None', 'Unknown'),
+    ]
+
+TestResult = LabTestResult                      # Backward compat
+
+from cocklebur.countries import country_optionexpr
+
+class Countries(core.DropList):
+    type_name = 'Countries'
+    choices = country_optionexpr
+
+from cocklebur.languages import language_optionexpr
+
+class Languages(core.DropList):
+    type_name = 'Languages'
+    choices = language_optionexpr
diff --git a/cocklebur/form_ui/jsmeta.py b/cocklebur/form_ui/jsmeta.py
new file mode 100644
index 0000000..1f1de36
--- /dev/null
+++ b/cocklebur/form_ui/jsmeta.py
@@ -0,0 +1,149 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+This logic collects metadata about questions to enable client-side skips
+(and potentially verification, etc).
+"""
+
+class JSSkip:
+    def __init__(self, name):
+        self.name = name
+        self.question = None
+        self.inverted = False
+        self.targets = []
+        self.inputs = []
+
+    def set_params(self, question, inverted):
+        self.question = question
+        self.inverted = inverted
+
+    def add_input(self, input, values):
+        assert isinstance(input, basestring)
+        self.inputs.append((input, list(values)))
+
+    def to_js(self, indent):
+        inputs_text = ['%s{name: %r, values: %r}' % (indent * 3, name, values)
+                       for name, values in self.inputs]
+        skip_text = []
+        skip_text.append('name: %r' % self.name)
+        skip_text.append('question: %r' % self.question)
+        skip_text.append('targets: %r' % self.targets)
+        skip_text.append('inverted: %s' % str(bool(self.inverted)).lower())
+        skip_text.append('inputs: [\n%s\n%s]' % 
+                            (',\n'.join(inputs_text), indent * 2))
+        skip_text = ['%s%s' % (indent * 2, l) for l in skip_text]
+        return '{\n%s}' % (',\n'.join(skip_text))
+
+    def add_target(self, name):
+        self.targets.append(name)
+
+
+class JSSkips:
+    def __init__(self):
+        self.skips = []
+        self.skips_by_name = {}
+
+    def get(self, name):
+        try:
+            skip = self.skips_by_name[name]
+        except KeyError:
+            skip = self.skips_by_name[name] = JSSkip(name)
+            self.skips.append(skip)
+        return skip
+
+    def to_js(self, indent='  '):
+        s_text = []
+        for skip in self.skips:
+            if skip.question:
+                s_text.append('%s%s' % (indent, skip.to_js(indent)))
+        return 'form_skips = [\n%s\n];\n' % (',\n'.join(s_text))
+
+
+class JSQuestion:
+    def __init__(self, label, trigger_mode, skips):
+        self.label = label
+        self.trigger_mode = trigger_mode
+        self.skips = skips
+        self.inputs = []
+        self.has_error = False
+        self.has_triggers = False
+
+    def add_inputs(self, inputs):
+        self.inputs.append(inputs)
+
+    def input_skip(self, name, input, values, inverted, skip_remaining):
+        """ One input with potentially multiple values """
+        skip = self.skips.get(name)
+        skip.set_params(self.label, inverted)
+        if skip_remaining:
+            skip.add_target(self.label)
+        self.has_triggers = True
+        skip.add_input(input, values)
+
+    def inputs_skip(self, name, inputs, inverted, skip_remaining):
+        """ Multiple inputs with boolean values (checkbox) """
+        skip = self.skips.get(name)
+        skip.set_params(self.label, inverted)
+        if skip_remaining:
+            skip.add_target(self.label)
+        self.has_triggers = True
+        for input in inputs:
+            skip.add_input(input, ['True'])
+
+    def trigger(self, name):
+        self.skips.get(name).add_target(self.label)
+        self.has_triggers = True
+
+    def to_js(self, indent):
+        if not self.has_triggers and not self.has_error:
+            return None
+        q_fields = [
+            'name: %r' % self.label,
+            'trigger_mode: %r' % self.trigger_mode,
+            'inputs: %r' % self.inputs,
+        ]
+        if self.has_error:
+            q_fields.append('error: true')
+        q_fields = [indent * 2 + f for f in q_fields] 
+        return '%s%r: {\n%s\n%s}' % \
+                    (indent, self.label, ',\n'.join(q_fields), indent)
+    
+
+class JSMeta:
+    def __init__(self):
+        self.questions = []
+        self.skips = JSSkips()
+
+    def question(self, label, trigger_mode):
+        question = JSQuestion(label, trigger_mode, self.skips)
+        self.questions.append(question)
+        return question
+
+    def to_js(self):
+        indent = '  '
+        output = ['\n']
+        q_text = []
+        for question in self.questions:
+            js = question.to_js(indent)
+            if js:
+                q_text.append(js)
+        output.append('form_data_version = 1\n')
+        output.append('form_questions = {\n%s\n};\n' % (',\n'.join(q_text)))
+        output.append(self.skips.to_js(indent))
+        return ''.join(output)
diff --git a/cocklebur/form_ui/pyload.py b/cocklebur/form_ui/pyload.py
new file mode 100644
index 0000000..ecd3be1
--- /dev/null
+++ b/cocklebur/form_ui/pyload.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur.form_ui.common import *
+
+def pyload(f):
+    l = {}
+    try:
+        exec f in l
+    except Exception:
+        e_type, e_value, e_tb = sys.exc_info()
+        msg = '%s: %s' % (e_type.__name__, e_value)
+        raise FormParseError, FormParseError(msg), e_tb
+    form = l['form']
+    if not form.form_type and 'form_type' in l:
+        form.form_type = l['form_type']
+    if not form.allow_multiple and 'allow_multiple' in l:
+        form.allow_multiple = l['allow_multiple']
+    return form
diff --git a/cocklebur/form_ui/pysave.py b/cocklebur/form_ui/pysave.py
new file mode 100644
index 0000000..f5b90e2
--- /dev/null
+++ b/cocklebur/form_ui/pysave.py
@@ -0,0 +1,98 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+__all__ = 'pysave', 
+
+def input(f, node, indent):
+    cls = node.__class__
+    clsname = cls.__name__
+    attr_list = [repr(node.column)]
+    ignore = 'column', 'error', 'table', 'choices'
+    attrs = vars(node)
+    for a, v in attrs.items():
+        if a not in ignore and v is not None:
+            if hasattr(cls, a) and getattr(cls, a) == v:
+                continue
+            attr_list.append('%s = %r' % (a, v))
+    choices = attrs.get('choices')
+    if choices:
+        choice_str = ['    %r,\n' % (c,) for c in node.choices]
+        choice_str.insert(0, 'choices = [\n')
+        choice_str.append(']')
+        attr_list.append(''.join(choice_str))
+    lines = ',\n'.join(attr_list).replace('\n', '\n' + ' ' * indent)
+    f.write('%s(%s),\n' % (clsname, lines))
+
+def question(f, node, parentvarname):
+    f.write('%s.question(\n' % parentvarname)
+    f.write('    text = %r,\n' % node.text)
+    if hasattr(node, 'help') and node.help:
+        f.write('    help = %r,\n' % node.help)
+    if hasattr(node, 'disabled') and node.disabled:
+        f.write('    disabled = True,\n')
+    if hasattr(node, 'triggers') and node.triggers:
+        if hasattr(node, 'trigger_mode'):
+            f.write('    trigger_mode = %r,\n' % node.trigger_mode)
+        f.write('    triggers = %r,\n' % list(node.triggers))
+    if len(node.inputs) == 1:
+        f.write('    input = ')
+        input(f, node.inputs[0], 8)
+    else:
+        f.write('    inputs = [\n')
+        for inp in node.inputs:
+            f.write(' ' * 8)
+            input(f, inp, 12)
+        f.write('    ],\n')
+    f.write(')\n')
+
+def section(f, node, clsname):
+    varname = clsname.lower()
+    args = [repr(node.text)]
+    if clsname == 'Form':
+        if node.form_type:
+            args.append('form_type=%r' % node.form_type)
+        if node.allow_multiple:
+            args.append('allow_multiple=%r' % node.allow_multiple)
+        if node.update_time:
+            args.append('update_time=%r' % node.update_time.strftime('%F %T'))
+        if node.username:
+            args.append('username=%r' % node.username)
+        if node.author:
+            args.append('author=%r' % node.author)
+    f.write('%s = %s(%s)\n' % (varname, clsname, ', '.join(args)))
+    for child in node.children:
+        if hasattr(child, 'children'):
+            if clsname == 'Form':
+                childclsname = 'Section'
+            else:
+                childclsname = 'Section'
+            section(f, child, childclsname)
+            f.write('%s.append(%s)\n' % (varname, childclsname.lower()))
+        elif hasattr(child, 'inputs'):
+            question(f, child, varname)
+
+def form(f, node):
+#    form_extra_cols[form_type].save(f, node.columns)
+    attrs = vars(node)
+    f.write('from cocklebur.form_ui import *\n')
+    f.write('from cocklebur import dbobj\n')
+    section(f, node, 'Form')
+
+def pysave(f, node):
+    form(f, node)
+
diff --git a/cocklebur/form_ui/xmlload.py b/cocklebur/form_ui/xmlload.py
new file mode 100644
index 0000000..4c47c42
--- /dev/null
+++ b/cocklebur/form_ui/xmlload.py
@@ -0,0 +1,231 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+
+from cocklebur.form_ui.common import *
+from cocklebur.form_ui import elements, inputbase, inputs
+from cocklebur import xmlparse
+
+class FormXMLParse(xmlparse.XMLParse):
+
+    root_tag = 'form'
+
+    # Input text attrs
+    class Label(xmlparse.SetAttrNode):
+        attr = 'label'
+
+    class Summary(xmlparse.SetAttrNode):
+        # Deprecated, use <label> instead
+        attr = 'summary'
+
+    class Pre_Text(xmlparse.SetAttrNode):
+        attr = 'pre_text'
+
+    class Post_Text(xmlparse.SetAttrNode):
+        attr = 'post_text'
+
+    class SkipValue(xmlparse.Node):
+        subtags = ()
+        permit_attrs = ('value',)
+
+        def end_element(self, parent):
+            value = self.attrs.get('value', '')
+            parent.values.append(value)
+
+    class Skip(xmlparse.Node):
+        __slots__ = 'values',
+        subtags = ('skipvalue')
+        permit_attrs = (
+            'mode', 'name', 'values', 'showmsg', 'show_msg', 'skipremaining'
+        )
+
+        def start_element(self, parent):
+            self.values = []
+
+        def end_element(self, parent):
+            not_selected = self.attrs.get('mode') == 'ifnotselected'
+            name = self.attrs.get('name')
+            if name is None:
+                parent.attrs['skip_mode'] = not_selected
+            else:
+                show_msg = (self.attrs.get('showmsg') or self.attrs.get('show_msg')) != 'no'
+                skip_remaining = self.attrs.get('skipremaining') != 'no'
+                skip = inputbase.Skip(name, self.values, not_selected,
+                                    show_msg=show_msg, 
+                                    skip_remaining=skip_remaining)
+                parent.skips.append(skip)
+
+    class Choice(xmlparse.Node):
+        subtags = ()
+        permit_attrs = ('name', 'skipon')
+
+        def end_element(self, parent):
+            value = self.attrs.get('name', '')
+            label = self.get_text()
+            if self.attrs.get('skipon') == 'yes':
+                parent.skipon.append(value)
+            parent.choices.append((value, label))
+
+    class Choices(xmlparse.Node):
+        __slots__ = 'choices', 'skipon'
+        subtags = ('choice',)
+
+        def start_element(self, parent):
+            self.choices = []
+            self.skipon = []
+
+        def end_element(self, parent):
+            parent.attrs['choices'] = self.choices
+            if self.skipon:
+                parent.attrs['skipon'] = tuple(self.skipon)
+
+    class Input(xmlparse.Node):
+        __slots__ = 'skips',
+        subtags = (
+            'choices', 'summary', 'label', 'pre_text', 'post_text', 'skip',
+        )
+        permit_attrs = (
+            'default', 'direction', 'maximum:float', 'maxsize:int', 
+            'minimum:float', 'name', 'required:bool', 'skips', 
+            'summarise:bool', 'type', 
+        )
+
+        def start_element(self, parent):
+            self.skips = []
+
+        def end_element(self, parent):
+            name = self.attrs.pop('name', None)
+            if not name:
+                raise xmlparse.ParseError('<input> tag requires a "name" attribute')
+            input_type = self.attrs.pop('type', None)
+            if not input_type:
+                raise xmlparse.ParseError('<input> tag requires a "type" attribute')
+            input_cls = getattr(inputs, input_type, None)
+            if input_cls is None or input_type not in inputs.__all__:
+                raise xmlparse.ParseError('Unknown <input> type %r' % input_type)
+            direction = self.attrs.pop('direction', None)
+            if direction:
+                self.attrs['direction'] = direction
+            skip_values = self.attrs.pop('skipon', None)
+            if skip_values:
+                skip_mode = self.attrs.pop('skip_mode', None)
+                self.skips = [inputbase.Skip(name, skip_values, skip_mode)]
+            if self.skips:
+                self.attrs['skips'] = self.skips
+            if 'summary' in self.attrs:
+                summary = self.attrs.pop('summary')
+                self.attrs['label'] = summary
+                self.attrs['summarise'] = bool(summary)
+            input = input_cls(name, **self.attrs)
+            parent.inputs.append(input)
+
+    class Label(xmlparse.SetAttrNode):
+        # Used by question, section and form
+        attr = 'label'
+
+    class Help(xmlparse.SetAttrNode):
+        # Used by question
+        attr = 'help'
+
+    class ExcludeIf(xmlparse.Node):
+        # Legacy
+        subtags = ()
+        permit_attrs = ('name',)
+
+        def end_element(self, parent):
+            name = self.attrs.get('name')
+            if name:
+                parent.triggers.append(name)
+
+    class IncludeIf(xmlparse.Node):
+        # Legacy
+        subtags = ()
+        permit_attrs = ('name',)
+
+        def end_element(self, parent):
+            name = self.attrs.get('name')
+            if name:
+                parent.triggers.append(name)
+                parent.attrs['trigger_mode'] = 'enable'
+
+    class Trigger(xmlparse.Node):
+        subtags = ()
+        permit_attrs = ('name',)
+
+        def end_element(self, parent):
+            name = self.attrs.get('name')
+            if name:
+                parent.triggers.append(name)
+
+    class Question(xmlparse.Node):
+        subtags = ('input', 'label', 'help', 'trigger') + \
+                    ('excludeif', 'includeif')      # Legacy
+        __slots__ = 'inputs', 'triggers'
+        permit_attrs = ('disabled:bool', 'trigger_mode')
+
+        def start_element(self, parent):
+            self.inputs = []
+            self.triggers = []
+
+        def end_element(self, parent):
+            label = self.attrs.pop('label', None)
+            if self.triggers:
+                self.attrs['triggers'] = self.triggers
+            question = elements.Question(label, inputs=self.inputs, 
+                                         **self.attrs)
+            parent.children.append(question)
+
+
+    class _Section(xmlparse.Node):
+        __slots__ = 'children', 
+        subtags = ('section', 'label', 'question')
+
+        def start_element(self, parent):
+            self.children = []
+
+
+    class Section(_Section):
+
+        def end_element(self, parent):
+            label = self.attrs.pop('label', None)
+            section = elements.Section(text=label, **self.attrs)
+            for child in self.children:
+                section.append(child)
+            parent.children.append(section)
+
+
+    class Form(_Section):
+        __slots__ = 'form', 
+        permit_attrs = (
+            'name', 'form_type', 'allow_multiple:bool', 'update_time', 
+            'author', 'username', 
+        )
+
+        def end_element(self, parent):
+            label = self.attrs.pop('label', None)
+            self.form = elements.Form(text=label, **self.attrs)
+            for child in self.children:
+                self.form.append(child)
+
+
+def xmlload(f):
+    try:
+        return FormXMLParse().parse(f).form
+    except xmlparse.ParseError, e:
+        raise FormParseError, e, sys.exc_info()[2]
diff --git a/cocklebur/form_ui/xmlsave.py b/cocklebur/form_ui/xmlsave.py
new file mode 100644
index 0000000..c3d4047
--- /dev/null
+++ b/cocklebur/form_ui/xmlsave.py
@@ -0,0 +1,130 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+### TODO - encode output as UTF-8
+
+from cocklebur.xmlwriter import XMLwriter
+
+__all__ = 'xmlsave',
+
+def text(x, tag, text):
+    e = x.push(tag)
+    e.text(text)
+    x.pop()
+
+def choice(x, name, value):
+    e = x.push('choice')
+    e.attr('name', name)
+    if value is not None:
+        e.text(value)
+    x.pop()
+
+def skip(x, skip):
+    if skip.values:
+        e = x.push('skip')
+        e.attr('name', skip.name)
+        if skip.not_selected:
+            e.attr('mode', 'ifnotselected')
+        if not skip.show_msg:
+            e.attr('showmsg', 'no')
+        if not skip.skip_remaining:
+            e.attr('skipremaining', 'no')
+        for value in skip.values:
+            e = x.push('skipvalue')
+            e.attr('value', value)
+            x.pop()
+        x.pop()
+
+def input(x, node):
+    def opttext(a):
+        v = attrs.get(a)
+        if v:
+            text(x, a, v)
+
+    attrs = vars(node)
+    e = x.push('input')
+    e.attr('name', node.column)
+    e.attr('type', node.__class__.__name__)
+    e.optattr(node, 'required')
+    e.optattr(node, 'summarise')
+    e.optattr(node, 'default')
+    e.optattr(node, 'minimum')
+    e.optattr(node, 'maximum')
+    e.optattr(node, 'maxsize')
+    if hasattr(node, 'direction'):
+        e.optattr(node, 'direction')
+    opttext('label')
+    opttext('pre_text')
+    opttext('post_text')
+    choices = attrs.get('choices')
+    if choices:
+        x.push('choices')
+        for value, label in choices:
+            choice(x, value, label)
+        x.pop()
+    skips = attrs.get('skips', ())
+    if skips:
+        for s in skips:
+            skip(x, s)
+    x.pop()
+
+def trigger(x, trigger):
+    e = x.push('trigger')
+    e.attr('name', trigger)
+    x.pop()
+
+def question(x, node):
+    e = x.push('question')
+    e.optattr(node, 'disabled')
+    if node.triggers:
+        e.optattr(node, 'trigger_mode')
+    text(x, 'label', node.text or '')
+    if node.help:
+        text(x, 'help', node.help)
+    if node.triggers:
+        for name in node.triggers:
+            trigger(x, name)
+    for child in node.inputs:
+        input(x, child)
+    x.pop()
+
+def _section(x, node):
+    text(x, 'label', node.text)
+    for child in node.children:
+        if hasattr(child, 'children'):
+            section(x, child)
+        elif hasattr(child, 'inputs'):
+            question(x, child)
+
+def section(x, node):
+    x.push('section')
+    _section(x, node)
+    x.pop()
+
+def xmlsave(f, node):
+    x = XMLwriter(f)
+    e = x.push('form')
+    e.attr('name', node.name)
+    e.attr('form_type', getattr(node, 'form_type', 'case'))
+    e.attr('allow_multiple', getattr(node, 'allow_multiple', False))
+    e.optattr(node, 'author')
+    e.optattr(node, 'username')
+    if node.update_time:
+        e.attr('update_time', node.update_time.strftime('%F %T'))
+    _section(x, node)
+    x.pop()
diff --git a/cocklebur/group_edit.py b/cocklebur/group_edit.py
new file mode 100644
index 0000000..15e8814
--- /dev/null
+++ b/cocklebur/group_edit.py
@@ -0,0 +1,80 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+class GroupEdit:
+    def __init__(self, db, group_membership):
+        self.group_membership = group_membership
+        self.group_map = {}
+        for group in db.query('groups').fetchall():
+            self.group_map[group.group_id] = group.group_name
+        self.excluded = self.group_map.copy()
+        self.included = {}
+        for group in self.group_membership:
+            group_id = group.group_id
+            try:
+                del self.excluded[group_id]
+            except KeyError:
+                pass
+            self.included[group_id] = self.group_map[group_id]
+        self.include_group = []
+        self.exclude_group = []
+
+    def get_included(self):
+        included = [(v, k) for k, v in self.included.items()]
+        included.sort()
+        return [(k, v) for v, k in included]
+
+    def get_excluded(self):
+        excluded = [(v, k) for k, v in self.excluded.items()]
+        excluded.sort()
+        return [(k, v) for v, k in excluded]
+
+    def page_process(self, ctx):
+        if ctx.req_equals('groups_add'):
+            for group_id in self.include_group:
+                group_id = int(group_id)
+                name = self.excluded[group_id]
+                del self.excluded[group_id]
+                self.included[group_id] = name
+        if ctx.req_equals('groups_del'):
+            for group_id in self.exclude_group:
+                group_id = int(group_id)
+                name = self.included[group_id]
+                del self.included[group_id]
+                self.excluded[group_id] = name
+
+    def db_update(self):
+        add_groups = self.included.copy()
+        for gm in tuple(self.group_membership):
+            if self.excluded.has_key(gm.group_id):
+                self.group_membership.remove(gm)
+            else:
+                try:
+                    del add_groups[gm.group_id]
+                except KeyError:
+                    pass
+        for group_id in add_groups.keys():
+            gm = self.group_membership.new_row()
+            self.new_member(gm)
+            gm.group_id = group_id
+            self.group_membership.append(gm)
+        self.group_membership.db_update()
+
+    def db_revert(self):
+        self.group_membership.db_revert()
diff --git a/cocklebur/hsv.py b/cocklebur/hsv.py
new file mode 100644
index 0000000..093d679
--- /dev/null
+++ b/cocklebur/hsv.py
@@ -0,0 +1,27 @@
+# http://www.cs.rit.edu/~ncs/color/t_convert.html
+
+def HSVtoRGB(h, s, v):
+    v = float(v)
+    if not s:
+        # achromatic (grey)
+        return v, v, v
+    h *= 6.0                    # sector 0 to 5
+    i = int(h)
+    f = h - i                   # factorial part of h
+    p = v * (1.0 - s)
+    q = v * (1.0 - s * f)
+    t = v * (1.0 - s * (1.0 - f))
+    if i == 0:
+        return v, t, p
+    elif i == 1:
+        return q, v, p
+    elif i == 2:
+        return p, v, t
+    elif i == 3:
+        return p, q, v
+    elif i == 4:
+        return t, p, v
+    elif i == 5:
+        return v, p, q
+    else:
+        raise ValueError('Hue outside 0<1 range')
diff --git a/cocklebur/introspect.py b/cocklebur/introspect.py
new file mode 100644
index 0000000..cad02ca
--- /dev/null
+++ b/cocklebur/introspect.py
@@ -0,0 +1,36 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import inspect
+
+def getclassattr(cls, attr):
+    for c in inspect.getmro(cls):
+        if hasattr(c, attr):
+            return getattr(c, attr)
+
+def callall(inst, methname, *args, **kwargs):
+    """
+    Call the named method (if it exists) on all base cases in the MRO
+
+    super() for new-style classes does something somewhat equivalent,
+    but not all our classes are new-style, and there are other caveats.
+    """
+    for cls in inspect.getmro(inst.__class__):
+        cls_meth = cls.__dict__.get(methname)
+        if cls_meth is not None:
+            cls_meth(inst, *args, **kwargs)
diff --git a/cocklebur/languages.py b/cocklebur/languages.py
new file mode 100644
index 0000000..bbcb584
--- /dev/null
+++ b/cocklebur/languages.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+languages = [
+    'Arabic',
+    'Armenian',
+    'Assyrian',
+    'Bosnian',
+    'Chinese',
+    'Croatian',
+    'Farsi/Persian',
+    'Filipino/Tagalog',
+    'French',
+    'German',
+    'Greek',
+    'Hindi',
+    'Hungarian',
+    'Indonesian',
+    'Italian',
+    'Japanese',
+    'Khmer/Cambodian',
+    'Korean',
+    'Lao',
+    'Macedonian',
+    'Maltese',
+    'Polish',
+    'Portuguese',
+    'Punjabi',
+    'Russian',
+    'Samoan',
+    'Serbian',
+    'Somali',
+    'Spanish',
+    'Thai',
+    'Tongan',
+    'Turkish',
+    'Ukrainian',
+    'Vietnamese',
+]
+
+language_optionexpr = [(l, l) for l in languages]
+language_optionexpr.insert(0, ('', 'None required'))
+search_language_optionexpr = [(l, l) for l in languages]
+search_language_optionexpr.insert(0, ('!', 'None required'))
+search_language_optionexpr.insert(0, ('', 'Any'))
diff --git a/cocklebur/pageops.py b/cocklebur/pageops.py
new file mode 100644
index 0000000..54d4aeb
--- /dev/null
+++ b/cocklebur/pageops.py
@@ -0,0 +1,325 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import traceback
+import csv
+from cStringIO import StringIO
+
+class Confirm(Exception):
+    mode = None
+    title = None
+    message = None
+    reason_prompt = None
+    reason = None
+    buttons = []
+
+    def __init__(self, mode=None, **kwargs):
+        if mode is not None:
+            self.mode = mode
+        self.__dict__.update(kwargs)
+
+    def set_action(self, action, args):
+        self.action = action
+        self.action_args = args
+
+    def button(self, pageops, ctx, button):
+        meth = getattr(self, 'button_' + button, None)
+        if meth:
+            meth(pageops, ctx)
+
+    def resume(self, pageops, ctx):
+        try:
+            pageops.confirmed = True
+            pageops.dispatch(ctx, self.action, self.action_args)
+        finally:
+            pageops.confirmed = False
+
+    def button_discard(self, pageops, ctx):
+        pageops.rollback(ctx)
+        self.resume(pageops, ctx)
+
+    def button_continue(self, pageops, ctx):
+        ctx.locals.confirm = None
+
+    def button_savefirst(self, pageops, ctx):
+        pageops.commit(ctx)
+        self.resume(pageops, ctx)
+
+    def button_confirm(self, pageops, ctx):
+        self.resume(pageops, ctx)
+
+
+class ConfirmSave(Confirm):
+    mode = 'save'
+    title = 'Unsaved changes?'
+    message = 'There are unsaved changes on this page'
+    buttons = [
+        ('discard', 'Discard Changes'),
+        ('continue', 'Continue Editing'),
+        ('savefirst', 'Save First'),
+    ]
+
+class ConfirmDelete(Confirm):
+    mode = 'delete'
+    message = 'Are you sure you wish to delete this record?'
+    buttons = [
+        ('continue', 'No, do not delete'),
+        ('discard',  'Yes, delete it'),
+    ]
+
+class ConfirmUndelete(Confirm):
+    mode = 'delete'
+    message = 'Are you sure you wish to undelete this record?'
+    buttons = [
+        ('continue', 'No, do not undelete'),
+        ('discard', 'Yes, undelete it'),
+    ]
+
+class ConfirmRevert(Confirm):
+    mode = 'revert'
+    title = 'Undo all changes'
+    message = 'Do you really wish to abandon all changes?'
+    buttons = [
+        ('continue', 'No, keep editing'),
+        ('discard', 'Yes, abandon'),
+    ]
+
+
+class ContinueDispatch: pass
+
+class PageOpsBase:
+    """
+    Albatross page_process helper/dispatcher
+
+    The page_process(ctx) method iterates over fields in the submitted
+    form.  The field name is split on the ':' character. If the first
+    component of the split field matches a method on this object with
+    'do_' prefixed, the method is called with arguments consisting of
+    the remaining split components.
+
+    A number of common do_ methods are implemented here:
+
+        do_back
+
+            Check for unsaved changes, call self.rollback and then pop a page
+
+        do_logout   
+        
+            Log out of the app
+
+        do_home
+
+            go to the "home" page (self.home_page)
+
+        do_confirm
+            
+            Dispatch actions associated with the "confirm" screen. If in
+            "confirm" mode, action names that do not start with "confirm"
+            are ignored.
+
+    Sub-classes should override the unsaved_check() method if they
+    need to intercept attempts to leave the page. This method should
+    raise Confirm(confirm_action) if confirmation is required.  The page
+    template should test for "confirm", and display an appropriate dialog
+    containing confirm_discard, confirm_continue, and confirm_savefirst
+    inputs.
+
+    Actions that need to be confirmed should call check_unsaved_or_confirmed().
+    This method will call the unsaved_check() method (potentially raising
+    a Confirm exception), but only if the associated action has not
+    already been confirmed.
+
+    """
+    home_page = 'home'
+    debug = False
+
+    def __init__(self, template=None):
+        self.confirmed = False
+        self.template = template
+
+    def unsaved_check(self, ctx):
+        """ 
+        Subclasses to overload this and raise Confirm if
+        they have unsaved data.
+        """
+        pass
+
+    def commit(self, ctx):
+        """ Subclasses to overload this if they need commit actions """
+        pass
+
+    def rollback(self, ctx):
+        """ Subclasses to overload this if they need cleanup actions """
+        pass
+
+    def check_unsaved_or_confirmed(self, ctx):
+        if not self.confirmed:
+            self.unsaved_check(ctx)
+
+    def do_logout(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.logout()
+
+    def do_home(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.pop_page(self.home_page)
+
+    def do_back(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        self.rollback(ctx)
+        ctx.pop_page()
+
+    def do_confirm(self, ctx, button):
+        confirm = ctx.locals.confirm
+        if confirm is not None:
+            confirm.button(self, ctx, button)
+            # getattr in case button is "logout", which empties ctx.locals
+            if getattr(ctx.locals, 'confirm', None) is confirm:
+                ctx.locals.confirm = None
+
+    def dispatch(self, ctx, meth_name, fields):
+        assert isinstance(fields, (list, tuple))
+        meth = getattr(self, 'do_' + meth_name)
+        if self.debug: 
+            print >> sys.stderr, 'page_proc meth %s%r' %\
+                (meth_name, tuple(fields))
+        try:
+            return meth(ctx, *fields)
+        except Confirm, confirm:
+            if self.debug:
+                tb = sys.exc_info()[2]
+                try:
+                    while tb.tb_next:
+                        tb = tb.tb_next
+                    print >> sys.stderr, 'Confirm from %s, line %s' % (
+                        tb.tb_frame.f_code.co_filename, tb.tb_lineno)
+                finally:
+                    del tb
+            if ctx.locals.confirm is not None:
+                confirm.set_action(ctx.locals.confirm.action,
+                                   ctx.locals.confirm.action_args)
+            else:
+                confirm.set_action(meth_name, fields)
+            ctx.locals.confirm = confirm
+
+    def page_process(self, ctx):
+        """
+        Attempt to dispatch the request to an appropriate "do_" method. 
+
+        Method arguments can be encoded in the field name, or in the
+        field value (the "value" attribute is used as the label for submit
+        buttons, so application values have to be encoded in their name).
+        """
+        if self.debug: 
+            print >> sys.stderr, 'fields: %r' % ctx.request.field_names()
+        for field_name in ctx.request.field_names():
+            # <input type="image"> returns fieldname.x and fieldname.y
+            if field_name.endswith('.x'):
+                field_name = field_name[:-2]
+            elif field_name.endswith('.y'):
+                continue
+            fields = field_name.split(':')
+            meth_name = fields.pop(0)
+            if (getattr(ctx.locals, 'confirm', False) 
+                and not meth_name.startswith('confirm')):
+                # Ignore non-confirm ops if we are confirming.
+                continue
+            if hasattr(self, 'do_' + meth_name):
+                if not fields:
+                    # If args not encoded in field name, try the field
+                    # value (how revolutionary!)
+                    value = ctx.request.field_value(field_name)
+                    if type(value) is list:
+                        # Browser can return multiple values for a field, but
+                        # some might be null
+                        fields = filter(None, value)
+                    elif value:
+                        fields = [value]
+                if fields:
+                    if self.dispatch(ctx, meth_name, fields)!=ContinueDispatch:
+                        return True
+        return False
+
+
+class DownloadBase(object):
+    def __init__(self, ctx, file_name, content_type=None):
+        self.file_name = file_name
+        if content_type is None:
+            content_type = 'application/unknown'
+        self.content_type = content_type
+        ctx.locals.download = self
+
+    def set_headers(self, ctx):
+        ctx.set_save_session(False)
+        # IE will not download via SSL if caching is disabled. 
+        # See: http://support.microsoft.com/?kbid=323308
+        ctx.del_header('Cache-Control')
+        ctx.del_header('Pragma')
+        ctx.set_header('Content-Type', self.content_type)
+        ctx.set_header('Content-Disposition', 
+                    'attachment; filename="%s"' % self.file_name)
+
+
+class download(DownloadBase):
+    def __init__(self, ctx, file_name, data=None,
+                    content_type='application/unknown'):
+        DownloadBase.__init__(self, ctx, file_name, content_type)
+        self.data = []
+        if data:
+            self.data.append(data)
+
+    def write(self, data):
+        self.data.append(data)
+
+    def send(self, ctx):
+        self.set_headers(ctx)
+        ctx.send_content(''.join(self.data))
+
+
+class csv_download(DownloadBase):
+    # text/csv is the "correct" MIME type, however this results in a "Save-As"
+    # dialog, and users want Excel to open directly, so we use the
+    # vendor-specific application/vnd.ms-excel instead, which works everywhere
+    # except OS X (where the filename gets mucked up).
+    def __init__(self, ctx, rowgen, file_name, 
+                 content_type='application/vnd.ms-excel'):
+        DownloadBase.__init__(self, ctx, file_name, content_type)
+        self.rowgen = rowgen
+
+    def send(self, ctx):
+        # Warning - We bypass albatross here, because csv.writer wants a
+        # file-like object, and it's a lot of inefficient mucking around to
+        # make send_content look like a file.
+        self.set_headers(ctx)
+        ctx.write_headers()
+        csv.writer(sys.stdout).writerows(self.rowgen)
+        sys.stdout.flush()
+
+
+def send_download(ctx):
+    download = getattr(ctx.locals, 'download', None)
+    if download:
+        try:
+            download.send(ctx)
+        except Exception:
+            # HTML body may have been partially emitted by the time an export
+            # exception occurs, so we can't render our usual friendly page.
+            traceback.print_exc(None, sys.stderr)
+        return True
+    return False
diff --git a/cocklebur/pt.py b/cocklebur/pt.py
new file mode 100644
index 0000000..6516567
--- /dev/null
+++ b/cocklebur/pt.py
@@ -0,0 +1,175 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+class _PTcommon:
+    def __init__(self, pt_set, label_col):
+        self.pt_set = pt_set
+        self.label_col = label_col
+        self.pt_available = []
+
+    def set_key(self, key):
+        self.pt_set.set_key(key)
+
+    def db_update(self):
+        self.pt_set.db_update()
+
+    def db_revert(self):
+        self.pt_set.db_revert()
+
+    def db_has_changed(self):
+        return self.pt_set.db_has_changed()
+
+    def __len__(self):
+        return len(self.pt_set)
+
+    def get_included(self):
+        included = [(getattr(self.pt_set[i], self.label_col), i)
+                    for i in range(len(self.pt_set))]
+        included.sort()
+        return [(i, l) for l, i in included]
+
+    def get_available(self):
+        available = [(getattr(self.pt_available[i], self.label_col), i)
+                    for i in range(len(self.pt_available))]
+        available.sort()
+        return [(i, l) for l, i in available]
+
+    def add(self, index):
+        self.pt_set.add(self.pt_available.pop(int(index)))
+
+    def remove(self, index):
+        row = self.pt_set.pop(int(index))
+        if row not in self.pt_available:
+            self.pt_available.append(row)
+
+    def db_desc(self):
+        added, removed = self.pt_set.changes()
+        if not added and not removed:
+            return
+        table = self.pt_set.pt_info.table_desc.name
+        keycol = self.pt_set.pt_info.master_col
+        fields = ['%s:%r' % (keycol, self.pt_set.key)]
+        for row in removed:
+            fields.append('-(%s)' % getattr(row, self.label_col))
+        for row in added:
+            fields.append('+(%s)' % getattr(row, self.label_col))
+        return '%s[%s]' % (table, ', '.join(fields))
+
+    dispatch = ()
+
+    def do(self, op, *args):
+        if op in self.dispatch:
+            meth = getattr(self, op)
+            meth(*args)
+
+
+class SearchPT(_PTcommon):
+    def __init__(self, pt_set, label_col, filter=None, name='pt_search',
+                 info_page=False):
+        _PTcommon.__init__(self, pt_set, label_col)
+        self.is_ordered = 0
+        self.filter = filter
+        self.name = name
+        self.info_page = info_page
+        self.clear_search()
+
+    def clear_search(self):
+        self.search_term = ''
+        self.clear_search_result()
+
+    def clear_search_result(self):
+        self.pt_available = []
+        self.clear_search_error()
+
+    def clear_search_error(self):
+        self.search_error = ''
+
+    def selected(self):
+        return self.pt_set
+
+    def search(self):
+        self.clear_search_result()
+        try:
+            table_dict = self.pt_set.get_slave_cache()
+            table_dict.query(order_by = self.label_col)
+            if self.search_term:
+                table_dict.where('%s ilike %%s' % self.label_col,
+                                 dbobj.wild(self.search_term))
+            if self.filter:
+                table_dict.where(self.filter)
+            self.pt_available = table_dict.fetch_preload(limit=100)
+        except dbobj.DatabaseError, e:
+            self.search_error = str(e)
+        else:
+            if not self.pt_available:
+                self.search_error = 'No matching entries found'
+
+    def clear(self):
+        self.clear_search()
+
+    def move_up(self, index):
+        self.pt_set.move_up(int(index))
+
+    def move_dn(self, index):
+        self.pt_set.move_down(int(index))
+
+    dispatch = 'search', 'clear', 'add', 'remove', 'move_up', 'move_dn'
+
+
+class OrderedSearchPT(SearchPT):
+    def __init__(self, pt_set, label_col, filter=None, name='pt_search'):
+        SearchPT.__init__(self, pt_set, label_col, filter, name)
+        self.is_ordered = 1
+
+    def get_included(self):
+        return [(i, getattr(self.pt_set[i], self.label_col))
+                for i in range(len(self.pt_set))]
+
+def highest_first(g):
+    g = [int(i) for i in g]
+    g.sort()
+    g.reverse()
+    return g
+
+class SelectPT(_PTcommon):
+    def __init__(self, pt_set, label_col, name='group_edit'):
+        _PTcommon.__init__(self, pt_set, label_col)
+        self.name = name
+        slave_pkey = self.pt_set.pt_info.slave_pkey
+        available = self.pt_set.get_slave_cache().copy()
+        for row in self.pt_set:
+            try:
+                del available[getattr(row, slave_pkey)]
+            except KeyError:
+                pass
+        self.pt_available = available.values()
+        self.include_group = []
+        self.exclude_group = []
+
+    def add(self):
+        for index in highest_first(self.include_group):
+            _PTcommon.add(self, index)
+        self.include_group = []
+
+    def remove(self):
+        for index in highest_first(self.exclude_group):
+            _PTcommon.remove(self, index)
+        self.exclude_group = []
+
+    dispatch = 'add', 'remove'
diff --git a/cocklebur/safehtml.py b/cocklebur/safehtml.py
new file mode 100644
index 0000000..028af74
--- /dev/null
+++ b/cocklebur/safehtml.py
@@ -0,0 +1,293 @@
+#
+# Copyright (C) 2005, Object Craft P/L, Melbourne, Australia.
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+# 
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+# 
+#     * Neither the name of Object Craft nor the names of its contributors
+#       may be used to endorse or promote products derived from this
+#       software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+"""
+A crude HTML parser that attempts to filter unsafe elements
+
+We build a parse tree, and chop off any nodes that correspond to unsafe
+tags, such as applet, frame, script, style.
+
+We also ignore any unsafe attributes on otherwise safe tags, such as any
+of the on* attributes, src, etc.
+
+This isn't 100% safe - in particular, I may have overlooked some way
+of sneaking hostile content through, or the parsing engine may get out
+of sync and not recognise as HTML something the browser does recognise.
+
+$Id: safehtml.py 1804 2006-05-29 06:15:19Z andrewm $
+$Source$
+"""
+import re
+
+debug = 0
+
+_sre_dblquote_nogrp = r'"(?:[^"\\]*(?:\\.[^"\\]*)*)"'   # eg, "hello \"bill\""
+_sre_sglquote_nogrp = r"'(?:[^'\\]*(?:\\.[^'\\]*)*)'"   # eg, 'sam\'s ball'
+_sre_attr = r'(?:' + \
+    r'\s+\w+=' + _sre_dblquote_nogrp + '|' + \
+    r'\s+\w+=' + _sre_sglquote_nogrp + '|' + \
+    r'\s+\w+=[^ >]+|' + \
+    r'\s+\w+' + ')'                                     # eg, attr="value"
+_sre_attrs = r'(' + _sre_attr + r'*)'                   # eg, a1="v1" a2="v2"
+_sre_tag = r'<(/?\w+)' + _sre_attrs + r'\s*(/?>)'       # a full tag
+_re_tag = re.compile(_sre_tag, re.I|re.M|re.S)
+
+_sre_attr_grp = r'(\w+)=('+_sre_dblquote_nogrp+'|'+_sre_sglquote_nogrp+'|(?:[^ >]+))|(\w+)'
+_re_attrib = re.compile(_sre_attr_grp, re.I|re.M)
+
+_re_dequote = re.compile(r'\\(.)')
+
+class Node:
+    name = ''
+    def __init__(self):
+        self.content = []
+
+    def add_content(self, n):
+        self.content.append(n)
+
+    def is_empty(self):
+        return 0
+
+    def is_named(self, name):
+        return self.name == name
+
+
+class Text(Node):
+    bad_entity_re = re.compile(r'&(?!#?[a-zA-Z0-9]{2,7};)|[<>]')
+
+    def __init__(self, text):
+        self.text = text
+
+    def to_html(self, out):
+        def replace(match):
+            return {'&': '&', '<': '<', '>': '>'}[match.group(0)] 
+        out(self.bad_entity_re.sub(replace, self.text))
+
+class Root(Node):
+    def to_html(self, out):
+        for c in self.content:
+            c.to_html(out)
+
+    def is_block(self):
+        return 1
+
+class Tag(Node):
+    # Any tags other than these, we junk them and their contents
+    tag_allow_list = [
+        # Some of these are particular badies (style, exe content):
+        # 'applet', 'button', 'frame', 'iframe', 'link', 'frameset', 'object',
+        # 'param', 'script', 'style', 'title', 'meta'
+        #
+        # No need for forms in this context
+        # 'form', 'input', 'select', 'map',
+
+        'a', 'abbr', 'acronym', 'address', 'area', 'b', 'base', 'bdo', 'big',
+        'br', 'caption', 'center', 'cite', 'code', 'col', 'dd', 'del',
+        'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font', 'h1', 'h2',
+        'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'i', 'img', 
+        'ins', 'kbd', 'label', 'legend', 'li', 
+        'menu', 'ol', 'option', 'p', 'pre', 'q', 's', 'samp', 
+        'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody',
+        'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var'
+    ]
+    tag_allow = dict([(v, None) for v in tag_allow_list])
+
+    # For these tags, we ignore the tag, but generate contents
+    tag_ignore_list = [
+        'html', 'head', 'body',
+    ]
+    tag_ignore = dict([(v, None) for v in tag_ignore_list])
+
+    # These are the attributes we allow - mainly we're screening attrs that
+    # automatically follow URL's, or have executable content.
+    attrs_allow_list = [
+        # 'target',
+        'abbr', 'accept-charset', 'accept', 'accesskey', 'action', 'align',
+        'alink', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding',
+        'cellspacing', 'char', 'charoff', 'charset', 'checked', 'clear',
+        'color', 'cols', 'compact', 'coords', 'datetime', 'dir', 'disabled',
+        'enctype', 'face', 'for', 'headers', 'height', 'href', 'hspace',
+        'http-equiv', 'id', 'ismap', 'label', 'lang', 'link', 'longdesc',
+        'marginheight', 'marginwidth', 'maxlength', 'method', 'multiple',
+        'name', 'nohref', 'noshade', 'nowrap', 'prompt', 'readonly', 'rows',
+        'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span',
+        'src', 'start', 'summary', 'tabindex', 'text', 'title', 'type',
+        'valign', 'value', 'version', 'vlink', 'width',
+    ]
+    attrs_allow  = dict([(v, None) for v in attrs_allow_list])
+
+    # Block level tags
+    block_tag_list = [
+        'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'dl', 'dir',
+        'menu', 'pre', 'listing', 'xmp', 'plaintext', 'address', 'blockquote',
+        'form', 'isindex', 'fieldset', 'table', 'hr', 'div', 'multicol',
+        'nosave', 'layer', 'align', 'center', 'noframes', 'area',
+        'html', 'head', 'title', 'body',
+    ]
+    block_tag  = dict([(v, None) for v in block_tag_list])
+
+    # These attrs don't have any contents, so we can assume them to be
+    # complete immediately.
+    empty_list = [
+        'area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img',
+        'input', 'isindex', 'link', 'meta', 'param',
+    ]
+    empty  = dict([(v, None) for v in empty_list])
+
+    def __init__(self, name):
+        Node.__init__(self)
+        self.name = name
+        self.attrs = []
+        self.bad = not self.tag_allow.has_key(self.name)
+        self.ignore = self.tag_ignore.has_key(self.name)
+        self.is_complete = 0
+
+    def is_named(self, name):
+        return self.name == name
+
+    def is_empty(self):
+        return self.empty.has_key(self.name)
+
+    def is_block(self):
+        return self.block_tag.has_key(self.name)
+
+    def set_attrs(self, attrs):
+        self.attrs = []
+        if self.bad:
+            return
+        bits = _re_attrib.split(attrs)
+        offset = 0
+        while 1:
+            attrbits = bits[offset: offset + 4]
+            if len(attrbits) < 4:
+                break
+            offset = offset + 4
+            white, name1, value, name2 = attrbits
+            if name1:
+                name = name1.lower()
+            else:
+                name = name2.lower()
+                value = None
+            if self.attrs_allow.has_key(name):
+                self.attrs.append((name, value))
+        if self.name == 'a':
+            self.attrs.append(('target', '"_blank"'))
+
+    def complete(self):
+        self.is_complete = 1
+
+    def to_html(self, out):
+        if self.bad:
+            return
+        if self.ignore:
+            for c in self.content:
+                c.to_html(out)
+        else:
+            out('<' + self.name)
+            for name, value in self.attrs:
+                if value != None:
+                    out( ' ' + name + '=' + value)
+                else:
+                    out( ' ' + name)
+            if not self.content:
+                out( ' />')
+            else:
+                out( '>')
+                for c in self.content:
+                    c.to_html(out)
+                if self.is_complete:
+                    out( '</' + self.name + '>')
+
+def parse(src_html):
+    TEXT, TAG, ATTR, CLOSE = range(4)
+    state = TEXT
+    root = Root()
+    node_stack = [root]
+    for part in _re_tag.split(src_html):
+        if debug: print state, part, node_stack[0].name
+        if state == TEXT:
+            if part:
+                node_stack[0].add_content(Text(part))
+            state = TAG
+        elif state == TAG:
+            tag_name = part.lower()
+            closing = (tag_name[0] == '/')
+            if not closing:
+                tag = Tag(tag_name)
+                if tag.is_block():
+                    if debug: print "  block tag"
+                    if node_stack[0].is_named(tag_name):
+                        # Block tag can't contain itself
+                        node_stack.pop(0).complete()
+                    while not node_stack[0].is_block():
+                        if debug: print "   pop inline", node_stack[0].name
+                        node_stack.pop(0).complete()
+                node_stack.insert(0, tag)
+                node_stack[1].add_content(tag)
+            else:
+                tag_name = tag_name[1:]
+                complete = []
+                while node_stack:
+                    complete.append(node_stack.pop(0))
+                    if complete[-1].is_named(tag_name):
+                        break
+                if not node_stack:
+                    node_stack = complete
+                elif complete:
+                    complete[-1].complete()
+            state = ATTR
+        elif state == ATTR:
+            if part and not closing:
+                node_stack[0].set_attrs(part)
+            state = CLOSE
+        elif state == CLOSE:
+            if part[0] == '/' or node_stack[0].is_empty():
+                node_stack.pop(0).complete()
+            state = TEXT
+    return root
+
+
+def safehtml(src_html):
+    class Output:
+        def __init__(self):
+            self.output = []
+
+        def add(self, s):
+            self.output.append(s)
+
+        def flush(self):
+            return ''.join(self.output)
+            self.output = []
+
+    output = Output()
+    tree = parse(src_html)
+    tree.to_html(output.add)
+    return output.flush()
diff --git a/cocklebur/tablesearch.py b/cocklebur/tablesearch.py
new file mode 100644
index 0000000..3276f4c
--- /dev/null
+++ b/cocklebur/tablesearch.py
@@ -0,0 +1,79 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+UI Logic to support simple searches of a table
+"""
+
+from cocklebur import dbobj
+
+class TableSearch:
+    def __init__(self, db, table, col, filter=None, title=None, 
+                 showcols=None, info_page=False):
+        self.db = db
+        self.table = table
+        self.filter = filter
+        self.title = title
+        self.col = col
+        if showcols is None:
+            self.showcols = (col,)
+        else:
+            self.showcols = showcols
+        self.info_page = info_page
+        self.clear_search()
+
+    def clear_search(self):
+        self.term = ''
+        self.clear_search_result()
+
+    def clear_search_result(self):
+        self.result = []
+        self.clear_search_error()
+
+    def clear_search_error(self):
+        self.search_error = ''
+
+    def search(self):
+        self.clear_search_result()
+        try:
+            query = self.db.query(self.table)
+            if self.filter:
+                query.where(self.filter)
+            if self.term:
+                query.where('%s ilike %%s' % self.col,
+                            dbobj.wild(self.term))
+            self.result = query.fetchall(limit=100)
+        except dbobj.DatabaseError, e:
+            self.search_error = str(e)
+        else:
+            if not self.result:
+                self.search_error = 'No matching entries found'
+
+    def clear(self):
+        self.clear_search()
+
+    def span(self):
+        span = len(self.showcols) + 1
+        if self.info_page:
+            span += 1
+        return span
+
+    def do(self, op, *args):
+        if op in ('search', 'clear'):
+            meth = getattr(self, op)
+            meth(*args)
diff --git a/cocklebur/template.py b/cocklebur/template.py
new file mode 100644
index 0000000..25a650a
--- /dev/null
+++ b/cocklebur/template.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import time
+import re
+
+import config
+
+
+template_re = re.compile(r'{{(\w+)}}')
+
+def expand_template(template, o=None, **kw):
+    def repl(match):
+        var = match.group(1)
+        if var == 'time':
+            return time.strftime('%H:%M:%S')
+        elif var == 'datetime':
+            return time.strftime('%Y-%m-%d %H:%M:%S')
+        elif var == 'date':
+            return time.strftime('%Y-%m-%d')
+        try:
+            return str(kw[var])
+        except KeyError:
+            pass
+        if o is not None:
+            try:
+                return str(getattr(o, var))
+            except AttributeError:
+                pass
+        try:
+            return str(getattr(config, var))
+        except AttributeError:
+            return '{{%s}}' % var
+    return template_re.sub(repl, str(template))
diff --git a/cocklebur/temporary_file.py b/cocklebur/temporary_file.py
new file mode 100644
index 0000000..d3c6f75
--- /dev/null
+++ b/cocklebur/temporary_file.py
@@ -0,0 +1,39 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+
+class TemporaryFile(file):
+    def __init__(self, filename, mode = 'wb'):
+        self.__filename = filename
+        self.__tmpname = filename + '.tmp.%d' % os.getpid()
+        file.__init__(self, self.__tmpname, mode)
+
+    def close(self):
+        file.close(self)
+        os.rename(self.__tmpname, self.__filename)
+
+    def abort(self):
+        if not self.closed:
+            try:
+                file.close(self)
+                os.unlink(self.__tmpname)
+            except (IOError, OSError):
+                pass
+
+    def __del__(self):
+        self.abort()
diff --git a/cocklebur/tests/Makefile b/cocklebur/tests/Makefile
new file mode 100644
index 0000000..1d48d74
--- /dev/null
+++ b/cocklebur/tests/Makefile
@@ -0,0 +1,7 @@
+PYTHON = python
+tests := $(wildcard *.py)
+
+all: $(tests)
+	for test in $(tests); do\
+		PYTHONPATH=../.. $(PYTHON) $${test};\
+	done
diff --git a/cocklebur/tests/dbobj.py b/cocklebur/tests/dbobj.py
new file mode 100644
index 0000000..3974934
--- /dev/null
+++ b/cocklebur/tests/dbobj.py
@@ -0,0 +1,315 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys
+import unittest
+from cocklebur.dbobj import *
+
+test_db = 'test'
+test_table = 'unittest'
+
+# Enable/Disable debugging messages from the dbobj implementation under test.
+sys.modules['cocklebur.dbobj'].debug = False
+
+class Test(unittest.TestCase):
+    def test_exprbuilder(self):
+        ea = []
+        e = ExprBuilder('and', ea)
+        self.failIf(e)
+
+        e.where('a = b')
+        self.assertEqual(str(e), '(a = b)')
+
+        e.where('c = %s', 'd')
+        self.assertEqual(str(e), '(a = b and c = %s)')
+        self.assertEqual(ea, ['d'])
+
+        sub = e.sub_expr()
+        sub.where('e = f')
+        sub.where('g = %s', 'h')
+        self.assertEqual(str(e), '(a = b and c = %s and (e = f or g = %s))')
+        self.assertEqual(ea, ['d', 'h'])
+
+        e.where('i = %s', 'j')
+        self.assertEqual(str(e), 
+                         '(a = b and c = %s and (e = f or g = %s) and i = %s)')
+        self.assertEqual(ea, ['d', 'h', 'j'])
+
+        ea = []
+        e = ExprBuilder('and', ea)
+        e.where('a = b')
+        e.where('c = %s', 'd')
+        sub = e.sub_expr()
+        sub.where('e = f')
+        sub.where('g = %s', 'h')
+        subsub = sub.sub_expr()
+        subsub.where('k = l')
+        subsub.where('m = %s', 'n')
+        e.where('i = %s', 'j')
+        self.assertEqual(str(e), 
+            '(a = b and c = %s and (e = f or g = %s or (k = l and m = %s)) and i = %s)')
+        self.assertEqual(ea, ['d', 'h', 'n', 'j'])
+
+    def test_connect(self):
+        db = DatabaseDescriber(database=test_db)
+        c = db.cursor()
+
+    def test_register_table(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('name', StringColumn)
+        finally:
+            db.rollback()
+
+    def test_make_table(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('name', StringColumn)
+            db.make_table(test_table)
+        finally:
+            db.rollback()
+
+    def test_make_table_serial(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            db.make_table(test_table)
+        finally:
+            db.rollback()
+
+    def test_insert(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('name', StringColumn)
+            td.column('age', IntColumn)
+            td.column('temperature', FloatColumn)
+            db.make_table(test_table)
+            row = db.new_row(test_table)
+            row.name = 'fred'
+            row.age = 23
+            row.temperature = 37.2
+            row.db_update()
+            row = db.query(test_table).where('name = %s', 'fred').fetchone()
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.name, 'fred')
+            self.assertEqual(row.age, 23)
+            self.assertEqual(row.temperature, 37.2)
+        finally:
+            db.rollback()
+
+    def test_update(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('name', StringColumn)
+            td.column('age', IntColumn)
+            td.column('temperature', FloatColumn)
+            db.make_table(test_table)
+            row = db.new_row(test_table)
+            row.name = 'fred'
+            row.age = 23
+            row.temperature = 37.2
+            row.db_update()
+            row = db.query(test_table).where('name = %s', 'fred').fetchone()
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.name, 'fred')
+            self.assertEqual(row.age, 23)
+            self.assertEqual(row.temperature, 37.2)
+            row.name = 'sam'
+            row.age = 24
+            row.db_update()
+            self.assertEqual(row.name, 'sam')
+            rows = db.query(test_table).fetchall()
+            self.assertEqual(len(rows), 1)
+            row = rows[0]
+            self.assertEqual(row.name, 'sam')
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.age, 24)
+            self.assertEqual(row.temperature, 37.2)
+        finally:
+            db.rollback()
+
+    def test_delete(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('name', StringColumn)
+            db.make_table(test_table)
+            row = db.new_row(test_table)
+            row.name = 'fred'
+            row.db_update()
+            row = db.query(test_table).where('name = %s', 'fred').fetchone()
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.name, 'fred')
+            row.db_delete()
+            row = db.query(test_table).where('name = %s', 'fred').fetchone()
+            self.assertEqual(row, None)
+        finally:
+            db.rollback()
+
+    def test_boolean(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('truth', BooleanColumn)
+            td.column('dare', BooleanColumn)
+            db.make_table(test_table)
+            row = db.new_row(test_table)
+            row.truth = 'True'
+            row.dare = 1
+            row.db_update()
+            row = db.query(test_table).where('id = %s', 1).fetchone()
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.truth, 'True')
+            self.assertEqual(row.dare, 'True')
+            row.truth = None
+            row.dare = 0
+            row.db_update()
+            rows = db.query(test_table).fetchall()
+            self.assertEqual(len(rows), 1)
+            row = rows[0]
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.truth, '')
+            self.assertEqual(row.dare, '')
+        finally:
+            db.rollback()
+
+    def test_DateTime(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('birthday', DateColumn)
+            td.column('lunchtime', TimeColumn)
+            td.column('start_work', DatetimeColumn)
+            db.make_table(test_table)
+            row = db.new_row(test_table)
+            row.birthday = '4/2/1998'
+            row.lunchtime = '13:23'
+            row.start_work = '3/1/2004 07:35'
+            row.db_update()
+            row = db.query(test_table).where('id = %s', 1).fetchone()
+            self.assertEqual(row.id, 1)
+            self.assertEqual(row.birthday, '04/02/1998')
+            self.assertEqual(row.lunchtime, '13:23')
+            self.assertEqual(row.start_work, '03/01/2004 07:35')
+        finally:
+            db.rollback()
+
+    def test_rows(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            td = db.new_table(test_table)
+            td.column('id', SerialColumn, primary_key=True)
+            td.column('name', StringColumn)
+            db.make_table(test_table)
+
+            # Populate table
+            row = db.new_row(test_table)
+            row.name = 'fred'
+            row.db_update()
+            row = db.new_row(test_table)
+            row.name = 'sam'
+            row.db_update()
+
+            # Query and compare result
+            rows = db.query(test_table, order_by = 'id').fetchall()
+            self.assertEqual(len(rows), 2)
+            self.assertEqual(rows[0].id, 1)
+            self.assertEqual(rows[1].id, 2)
+            self.assertEqual(rows[0].name, 'fred')
+            self.assertEqual(rows[1].name, 'sam')
+
+            # Update, query result
+            rows[0].name = 'james'
+            rows.db_update()
+            rows = db.query(test_table, order_by = 'id').fetchall()
+            self.assertEqual(len(rows), 2)
+            self.assertEqual(rows[0].id, 1)
+            self.assertEqual(rows[0].name, 'james')
+
+            # Delete a row, query result
+            del rows[0]
+            rows.db_update()
+            rows = db.query(test_table, order_by = 'id').fetchall()
+            self.assertEqual(len(rows), 1)
+            self.assertEqual(rows[0].id, 2)
+        finally:
+            db.rollback()
+
+    def test_ref_column(self):
+        db = DatabaseDescriber(database=test_db)
+        try:
+            # Create dependent tables
+            foreign_table = 'other_test'
+            td = db.new_table(foreign_table)
+            td.column('label', StringColumn, primary_key = True)
+            td.column('description', StringColumn)
+            db.make_table(foreign_table)
+            td = db.new_table(test_table)
+            td.column('other', ReferenceColumn, references = foreign_table)
+            db.make_table(test_table)
+
+            # Populate
+            label = 'foo'
+            description = 'Foo you later, freak!'
+            other_row = db.new_row(foreign_table)
+            other_row.label = label
+            other_row.description = description
+            other_row.db_update()
+            row = db.new_row(test_table)
+            row.other = other_row.label
+            row.db_update()
+
+            # Query
+            row = db.query(test_table).fetchone()
+            self.assertEqual(row.get_ref('other').label, label)
+            self.assertEqual(row.get_ref('other').description, description)
+        finally:
+            db.rollback()
+
+class TestSuite(unittest.TestSuite):
+    test_list = (
+        'test_exprbuilder',
+        'test_connect',
+        'test_register_table',
+        'test_make_table',
+        'test_make_table_serial',
+        'test_insert',
+        'test_update',
+        'test_delete',
+        'test_boolean',
+        'test_DateTime',
+        'test_rows',
+        'test_ref_column',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Test, self.test_list))
+
+def suite():
+    return TestSuite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/cocklebur/trafficlight.py b/cocklebur/trafficlight.py
new file mode 100644
index 0000000..11e22ed
--- /dev/null
+++ b/cocklebur/trafficlight.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import hsv
+
+_colmap = ['#c6d2ff', '#c6ddff', '#c6e8ff', '#c6f3ff', '#c6ffff', 
+           '#c6fff3', '#c6ffe8', '#c6ffdd', '#c6ffd2', '#c6ffc6', 
+           '#d2ffc6', '#ddffc6', '#e8ffc6', '#f3ffc6', '#ffffc6', 
+           '#fff3c6', '#ffe8c6', '#ffddc6', '#ffd2c6', '#ffc6c6']
+ 
+
+def web_trafficlight(n, limit=100):
+    scale = len(_colmap) / float(limit)
+    return _colmap[max(0, min(int(n * scale), len(_colmap) - 1))]
+
+def make_n_colors(n, sat=0.3):
+    return ['#%02x%02x%02x' % hsv.HSVtoRGB(float(i) / n, sat, 255.0) 
+            for i in range(n)]
diff --git a/cocklebur/tuplestruct.py b/cocklebur/tuplestruct.py
new file mode 100644
index 0000000..6ccfca6
--- /dev/null
+++ b/cocklebur/tuplestruct.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+class TupleStruct(object):
+    __slots__ = ()
+
+    def __init__(self, *args, **kw):
+        if args:
+            if len(args) != len(self):
+                raise TypeError('%s requires %d values, %d given' % 
+                                (self.__class__.__name__, len(self), len(args)))
+            for a, v in zip(self.__slots__, args):
+                setattr(self, a, v)
+        elif kw:
+            if len(kw) != len(self):
+                raise TypeError('%s requires %d values, %d given' % 
+                                (self.__class__.__name__, len(self), len(kw)))
+            for a, v in kw.items():
+                setattr(self, a, v)
+
+    def __getstate__(self):
+        return [getattr(self, a) for a in self.__slots__]
+
+    def __setstate__(self, state):
+        assert len(state) == len(self.__slots__)
+        for a, v in zip(self.__slots__, state):
+            setattr(self, a, v)
+
+    def __getitem__(self, i):
+        return getattr(self, self.__slots__[i])
+
+    def __setitem__(self, i, v):
+        setattr(self, self.__slots__[i], v)
+
+    def __len__(self):
+        return len(self.__slots__)
+
+    def __repr__(self):
+        values = ['%s=%r' % (a, getattr(self, a)) for a in self.__slots__]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(values))
+
+if __name__ == '__main__':
+    class SS(TupleStruct): __slots__ = 'a', 'b'
+    ss = SS(3, 4)
+    assert (ss.a, ss.b) == (3, 4)
+    assert tuple(ss) == (3, 4)
+    try:
+        ss.x
+    except AttributeError:
+        pass
+    else:
+        assert 0, "attribute error not raised"
diff --git a/cocklebur/utils.py b/cocklebur/utils.py
new file mode 100644
index 0000000..96fb5b0
--- /dev/null
+++ b/cocklebur/utils.py
@@ -0,0 +1,104 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import re
+import random
+
+def commalist(fields, conjunction='or'):
+    """
+    Return the iterable /fields/ as a comma separated list, with the
+    last elements joined by "and/or" if appropriate.
+    """
+    if not fields:
+        return 'n/a'
+    fields = [str(f) for f in fields]
+    if len(fields) == 1:
+        return fields[0]
+    return '%s %s %s' % (', '.join(fields[:-1]), conjunction, fields[-1])
+
+
+comma_re = re.compile('\s*,\s*')
+def commasplit(buf):
+    return comma_re.split(buf.strip())
+
+
+safeprint_map = []
+for i in range(0, 256):
+    if 32 <= i < 127:
+        safeprint_map.append(chr(i))
+    else:
+        safeprint_map.append('?')
+safeprint_map = ''.join(safeprint_map)
+
+
+def safeprint(s):
+    return s.translate(safeprint_map)
+
+
+def randfn(base, ext):
+    chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+    noise = ''.join([random.choice(chars) for i in range(8)])
+    return '%s%s.%s' % (base, noise, ext)
+
+
+def nssetattr(ns, attr, value):
+    names = attr.split('.')
+    for name in names[:-1]:
+        ns = getattr(ns, name)
+    setattr(ns, names[-1], value)
+
+
+def nsgetattr(ns, attr):
+    for name in attr.split('.'):
+        ns = getattr(ns, name)
+    return ns
+
+
+def secret(nbits=256):
+    fd = os.open('/dev/urandom', os.O_RDONLY)
+    try:
+        return os.read(fd, nbits / 8)
+    finally:
+        os.close(fd)
+
+#addr_re = re.compile(r'^\s*([a-z0-9._%+-]+)@((?:[a-z0-9-])+(?:\.(?:[a-z0-9-])+)+)\s*$', re.I)
+
+rfc822_comment_re = re.compile(r'"[^"]*"|\([^)]*\)')
+rfc822_user_part = r'([a-z0-9._%+-]+)'
+rfc822_domain_part = r'((?:[a-z0-9-])+(?:\.(?:[a-z0-9-])+)+)\.?'
+rfc822_user_at_domain = rfc822_user_part + '@' + rfc822_domain_part
+rfc822_delim_addr = r'^[^<>]*<' + rfc822_user_at_domain + r'>[^<>]*$'
+rfc822_delim_addr_re = re.compile(rfc822_delim_addr, re.I)
+rfc822_bare_addr = r'^\s*' + rfc822_user_at_domain + r'\s*$'
+rfc822_bare_addr_re = re.compile(rfc822_bare_addr, re.I)
+
+def parse_addr(addr):
+    """
+    Attempt to parse an RFC822 e-mail address (optionally including
+    comments, which are striped). Returns a tuple of (user, domain)
+    or raises ValueError.
+    """
+    match = rfc822_delim_addr_re.match(addr)
+    if match:
+        return match.group(1).lower(), match.group(2).lower()
+    addr_no_comments = ''.join(rfc822_comment_re.split(addr))
+    match = rfc822_bare_addr_re.match(addr_no_comments)
+    if match:
+        return match.group(1).lower(), match.group(2).lower()
+    raise ValueError('Invalid e-mail address: %r' % addr)
diff --git a/cocklebur/xmlparse.py b/cocklebur/xmlparse.py
new file mode 100644
index 0000000..2049814
--- /dev/null
+++ b/cocklebur/xmlparse.py
@@ -0,0 +1,261 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+This lightweight expat-based XML parser acts as a facilitator for
+building trees of external objects, rather than producing a tree of it's
+own objects.
+
+For each element found, a (user supplied) Node subclass is
+instantiated. Subclasses are expected to override one or more of the
+event methods to achieve their ends:
+
+    start_element(parent)
+    end_child(child)
+    end_element(parent)
+
+Element attributes appear on the Node instance in the .attrs property.
+
+Text contained in an element can be retrieved with the .get_text() method.
+
+A ContainerNode class if provided as a basis for elements that collect
+up their children (nodes).
+
+A SetAttrNode class is provided for elements that simply set an attr on
+their parent. The attr name should be set in your subclass with the /attr/
+class property.
+
+Node instances are slotted for performance - if you need to add extra
+attributes, you will need to register extra __slots__.
+
+Examples of use include:
+
+    cocklebur/form_ui/xmlload.py
+    casemgr/dataimp/xmlload.py
+    casemgr/reports/xmlload.py
+"""
+
+import sys
+try:
+    set
+except NameError:
+    from sets import Set as set
+from xml.parsers import expat
+
+__all__ = (
+    'Node', 'ContainerNode' 'SetAttrNode', 
+    'XMLParse', 'ParseError', 
+)
+
+class ParseError(expat.ExpatError): pass
+
+bool_values = {
+    'yes': True,
+    'true': True,
+    'no': False,
+    'false': False,
+    '': False,
+}
+
+def bool_cvt(v):
+    try:
+        return bool_values[v.lower()]
+    except KeyError:
+        raise ParseError('invalid boolean value: %r' % v)
+
+attr_cvt = {
+    'int': int,
+    'long': int,
+    'float': float,
+    'bool': bool_cvt,
+    'str': str,
+    'unicode': unicode,
+}
+
+class NodeMeta(type):
+
+    def __new__(meta, name, bases, vars):
+        vars.setdefault('__slots__', ())
+        cls = type.__new__(meta, name, bases, vars)
+        cls.name = name.lower()
+        cls.subtabs = set(cls.subtags)
+        cls.attr_cvt = {}
+        for attr in (cls.required_attrs + cls.permit_attrs):
+            try:
+                attr, attr_type = attr.split(':', 1)
+            except ValueError:
+                cvt = None
+            else:
+                try:
+                    cvt = attr_cvt[attr_type]
+                except KeyError:
+                    raise TypeError('unknown type conversion for %s '
+                                    'attribute: %s' % (attr, attr_type))
+            cls.attr_cvt[attr] = cvt
+        cls.required_attrs = tuple([a.split(':')[0] 
+                                    for a in cls.required_attrs])
+        return cls
+
+
+class Node(object):
+
+    __metaclass__ = NodeMeta
+    __slots__ = 'attrs', 'text'
+    required_attrs = ()
+    permit_attrs = ()
+    subtags = ()
+
+    def __init__(self, attrs):
+        self.attrs = {}
+        for attr in self.required_attrs:
+            if attr not in attrs:
+                raise ParseError('<%s> tag requires %r attribute' %
+                                    (self.name, attr))
+        for attr, value in attrs.items():
+            try:
+                attr_cvt = self.attr_cvt[attr]
+            except KeyError:
+                raise ParseError('unknown attribute %r on <%s> tag' % 
+                                    (attr, self.name))
+            if attr_cvt is not None:
+                try:
+                    value = attr_cvt(value)
+                except Exception, e:
+                    raise ParseError('<%s>, %s=%r: %s' % 
+                                     (self.name, attr, value, e))
+            self.attrs[str(attr)] = value
+        self.text = []
+
+    def start_element(self, parent):
+        pass
+
+    def end_element(self, parent):
+        pass
+
+    def end_child(self, child):
+        pass
+
+    def cdata(self, data):
+        self.text.append(data)
+
+    def get_text(self):
+        return ''.join(self.text)
+
+
+class SetAttrNode(Node):
+    subtags = ()
+    attr = None
+
+    def end_element(self, parent):
+        text = self.get_text()
+        if text:
+            parent.attrs[self.attr] = text
+
+
+class ContainerNode(Node):
+    __slots__ = 'children', 
+
+    def start_element(self, parent):
+        self.children = []
+    
+    def end_child(self, child):
+        self.children.append(child)
+
+
+class XMLParseMeta(type):
+    def __init__(cls, name, bases, dict):
+        if name != 'XMLParse':
+            # Collect up the names of the contained nodes
+            cls.handlers = {}
+            for base in bases:
+                base_handlers = getattr(base, 'handlers', None)
+                if base_handlers:
+                    cls.handlers.update(base_handlers)
+            for k, v in dict.iteritems():
+                try:
+                    if not k.startswith('_') and issubclass(v, Node):
+                        cls.handlers[v.name] = v
+                except TypeError:
+                    pass
+
+
+class XMLParse(object):
+    __metaclass__ = XMLParseMeta
+    root_tag = None
+
+    def __init__(self):
+        self.parser = expat.ParserCreate()
+        # too hard for now - all returns are unicode
+        self.parser.returns_unicode = False
+        self.parser.StartElementHandler = self.start_element
+        self.parser.EndElementHandler = self.end_element
+        self.parser.CharacterDataHandler = self.cdata
+        self.stack = []
+        self.root = None
+
+    def parse(self, f):
+        try:
+            self.parser.ParseFile(f)
+        except expat.ExpatError, e:
+            e = str(e)
+            if ': line ' not in e:
+                e = '%s: line %s' % (e, self.parser.ErrorLineNumber)
+            if len(self.stack) > 1:
+                e += ', in %s' % self.stack[-1].name
+            raise ParseError, e, sys.exc_info()[2]
+        # expat catches both of these, but it's better to be safe...
+        if self.root is None:
+            raise ParseError('No top level tag seen')
+        if self.stack:
+            raise ParseError('Stack unbalanced')
+        return self.root
+
+    def start_element(self, name, attrs):
+        try:
+            tag_class = self.handlers[name]
+        except KeyError:
+            raise ParseError('unknown tag: <%s>' % name)
+        tag = tag_class(attrs)
+        if self.stack:
+            parent = self.stack[-1]
+            if name not in parent.subtags:
+                raise ParseError('<%s> not allowed within <%s>' % 
+                                    (name, parent.name))
+        else:
+            if self.root_tag is not None and name != self.root_tag:
+                raise ParseError('<%s> not allowed at top level' % name)
+            parent = None
+            if self.root is not None:
+                raise ParseError('already seen top level <%s>' % name)
+            self.root = tag
+        tag.start_element(parent)
+        self.stack.append(tag)
+
+    def end_element(self, name):
+        child = self.stack.pop(-1)
+        parent = None
+        if self.stack:
+            parent = self.stack[-1]
+        child.end_element(parent)
+        if parent is not None:
+            parent.end_child(child)
+
+    def cdata(self, data):
+        element = self.stack[-1]
+        if element:
+            element.cdata(data)
diff --git a/cocklebur/xmlwriter.py b/cocklebur/xmlwriter.py
new file mode 100644
index 0000000..86a5914
--- /dev/null
+++ b/cocklebur/xmlwriter.py
@@ -0,0 +1,127 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+### TODO - encode output as UTF-8
+
+from cocklebur import introspect
+
+def quoteattr(data):
+    # We'd really rather use a stdlib function, but the only one I can find is
+    # xml.sax.saxutils.quoteattr, and importing that module pulls in other
+    # modules which cause us problems.
+    data = data.replace("&", "&")
+    data = data.replace(">", ">")
+    data = data.replace("<", "<")
+    data = data.replace("\n", "
")
+    data = data.replace("\r", "
")
+    data = data.replace("\t", "	")
+    if '"' in data:
+        if "'" in data:
+            data = '"%s"' % data.replace('"', """)
+        else:
+            data = "'%s'" % data
+    else:
+        data = '"%s"' % data
+    return data
+
+
+class XMLelement(object):
+
+    indent = ' '
+
+    def __init__(self, name, f, level):
+        self.name = name
+        self.f = f
+        self.level = level
+        self.opened = False
+        self.attributes = []
+        self.savedtext = []
+
+    def attr(self, attr, value):
+        self.attributes.append((attr, value))
+
+    def boolattr(self, attr, value):
+        if str(value).lower() in ('true', 'yes'):
+            self.attr(attr, 'yes')
+        else:
+            self.attr(attr, 'no')
+
+    def optattr(self, node, attr):
+        value = getattr(node, attr, None) 
+        if value != introspect.getclassattr(node.__class__, attr):
+            self.attr(attr, value)
+
+    def _attr_str(self):
+        return ''.join([' %s=%s' % (a, quoteattr(str(v))) 
+                        for a, v in self.attributes])
+
+    def _write(self, s):
+        print >> self.f, '%s%s' % (self.indent * self.level, s)
+        
+    def opentag(self):
+        if not self.opened:
+            self._write('<%s%s>' % (self.name, self._attr_str()))
+            self.opened = True
+
+    def closetag(self):
+        if not self.opened and not self.savedtext:
+            self._write('<%s%s />' % (self.name, self._attr_str()))
+            return
+        text = None
+        if self.savedtext:
+            text = ''.join(self.savedtext)
+            if '<' in text or '>' in text or '&' in text:
+                text = '<![CDATA[' + text.replace(']]>', ']]>') + ']]>'
+        if not self.opened and text:
+            self._write('<%s%s>%s</%s>' % (self.name, self._attr_str(), 
+                                            text, self.name))
+            return
+        else:
+            self.opentag()
+            if text:
+                # This will result in spurious leading and trailing NL, but
+                # this case only occurs for tags with mixed subtags and text.
+                print >> self.f, text
+            self._write('</%s>' % self.name)
+
+    def text(self, s):
+        self.savedtext.append(str(s))
+
+
+class XMLwriter(object):
+
+    def __init__(self, f):
+        self.f = f
+        self.stack = []
+        print >> self.f, '<?xml version="1.0"?>'
+
+    def push(self, name):
+        if self.stack:
+            self.stack[-1].opentag()
+        e = XMLelement(name, self.f, len(self.stack))
+        self.stack.append(e)
+        return e
+
+    def pop(self):
+        self.stack.pop(-1).closetag()
+
+    def pushtext(self, name, value):
+        if value:
+            e = self.push(name)
+            e.text(value)
+            self.pop()
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..05d596d
--- /dev/null
+++ b/config.py
@@ -0,0 +1,164 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# ==============================================================================
+# Branding
+apptitle = 'NetEpi Collection'
+subbanner = 'Network-enabled tools for epidemiology and public health practice.'
+syndrome_label = 'Case Definition'
+unit_label = 'Role'
+group_label = 'Context'
+contact_label = 'Association'
+person_label = 'Person'
+helpdesk_contact = 'the NetEpi Helpdesk'
+login_helpdesk_contact = helpdesk_contact
+
+# ==============================================================================
+# Instance selection (Virtualisation)
+appname = 'collection'
+# Defaults to '::{appname}:'
+#dsn = '::collection:'
+session_server = 'localhost:34343'
+session_timeout = 3600
+# Defaults to OFF
+#registration_notify = 'nobody at example.com'
+#exception_notify = 'nobody at example.com'
+
+# ==============================================================================
+# Application behavior
+
+# Suggest using priority, name, syndrome_id or post_date. Append ' DESC' to
+# reverse order of an item, for example: 'post_date DESC, priority'
+order_syndromes_by = 'priority, name'
+
+# Duplicate-person search done within syndrome or across all syndromes?
+dup_per_syndrome = False
+
+# Attempt to roll-forward form data when the form is upgraded (False has not
+# been tested in some time and is no longer recommended).
+form_rollforward = True
+
+# Immediately create cases and contacts from search results, rather than 
+# requiring an explicit "create" from the case/contact screen.
+immediate_create = True
+
+# If True, show all syndromes on main page, not just those the user has
+# access to.
+show_all_syndromes = False
+
+# Exploit <iframe> side-effects to intercept browser <back> button use
+nobble_back_button = True
+
+# Date parsing and formatting. Choose one of DMY, MDY or ISO
+date_style = 'DMY'
+
+# Cache form summaries - If True, form summaries are only generated when a form
+# is updated. If False, they are generated on demand (which requires loading
+# the form definition and form instances for each summary generated). The main
+# drawback of caching summaries is dates are formatted according to the editing
+# user style preferences, rather than the viewing user's style preference
+# (which can be particularly confusing when one uses date style DMY and the
+# other uses MDY).
+cache_form_summaries = False
+
+# If matplotlib is available, this option can be set to True, and additional
+# graphing functions will become available.
+enable_matplotlib = False
+
+# When many demographic fields are enabled, the screen can become quite
+# cluttered. If the count of enabled fields exceeds the threshold given here,
+# the application switches to a tabbed rendering of the demographic fields,
+# where fields are grouped together by function, and the groups then rendered
+# inside tabs. To disable the tabbed rendering, set to 999.
+tabbed_demogfields_threshold = 20
+
+# If non-zero, after servicing this many requests, we exit gracefully to
+# minimise the impact of memory fragmentation and object leaks (only relevant
+# for persistent application servers).
+max_requests = 1000
+
+# ==============================================================================
+# User controls
+
+# If True, allow users to view details of other users of the system (subject to
+# that user's privacy settings).
+user_browser = True
+
+# User details check interval in days - if the user has not updated their
+# details in this time, remind them to review them. Set it to 0 to disable 
+# the details check.
+user_check_interval = 30
+
+# New user registration mode. Choices are:
+#
+# none          Users can only be added by admins.
+#
+# register      A button on the login page invites the user to register their
+#               details. After registering, their details are reviewed by an
+#               admin prior to their account being enabled.
+#
+# invite        An existing user invites a prospective user onto the system 
+#               via a one-time URL that takes the prospective user to a
+#               registration screen.  After registering, their details are
+#               reviewed by an admin prior to their account being enabled.
+#
+# sponsor       An existing user sponsors a prospective user onto the system
+#               via a one-time URL that takes the prospective user to a
+#               registration screen. After registering, the sponsoring user
+#               verifies the identity of the new user and then enables their
+#               access.
+# 
+user_registration_mode = 'register'
+
+# ==============================================================================
+# Change Notification Daemon
+#
+# The optional notification daemon makes certain data changes propagate between
+# application server instances faster. Application processes send the
+# notification daemon messages to announce that a certain object has changed,
+# and all interested clients then receive a copy of this notification, and
+# should discard any cached copies of the referenced object.
+#
+# Each NetEpi Collection instance should have it's own notification daemon.
+
+# Which server the notification daemon runs on:
+#
+#   If set to "none", no notification daemon will be started and time-based
+#   cache expiry is used. 
+#
+#   If set to "local", a daemon local to this instance will be automatically
+#   started if one is not already running, with communications occuring over
+#   unix domain sockets.
+#
+#   If set to a hostname or ip address, application processes will attempt to
+#   connect to that address and no local daemon will be started (use the
+#   stand-alone daemon on the server host).
+notification_host = 'local'
+
+# If connecting to a non-local notification daemon, this is the port to use:
+notification_port = 13535
+
+# ==============================================================================
+# Developer/Debugging
+debug = False
+tracedb = False
+install_verbose = False
+install_debug = False
+create_db = True
+compile_py = True
+exec_timing = False
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index eb95686..0000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,8 +0,0 @@
-netepi-collection for Debian
-----------------------------
-
-NetEpi Collection is one part of the NetEpi suite. It basically
-contains netepi-casemgr. PLease have a look at netepi-analysis
-and netepi-phredss as well.
-
- -- Andreas Tille <tille at debian.org>, Thu, 16 Jun 2005 12:01:09 +0200
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 2f4c234..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-netepi-collection (1.8.4-1) unstable; urgency=low
-
-  * Initial release Closes: #nnnn  (nnnn is the bug number of your ITP)
-
- -- Andreas Tille <tille at debian.org>  Thu,  8 Mar 2007 23:01:39 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index e01b78c..0000000
--- a/debian/control
+++ /dev/null
@@ -1,57 +0,0 @@
-Source: netepi-collection
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>,
-           Varun Hiremath <varun at debian.org>
-Section: science
-Priority: optional
-Build-Depends: debhelper (>= 10),
-               cdbs,
-               python-numeric-ext
-Standards-Version: 3.9.8
-Vcs-Browser: http://anonscm.debian.org/viewvc/debian-med/trunk/packages/netepi-collection/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/netepi-collection/trunk/
-Homepage: https://github.com/timchurches/NetEpi-Collection
-
-Package: netepi-collection
-Architecture: all
-Depends: ${shlibs:Depends},
-         ${misc:Depends},
-         apache2,
-         python,
-         python-albatross,
-         python-egenix-mxdatetime,
-         postgresql,
-         python-pgsql
-Recommends: dia,
-            libapache2-mod-fcgid
-Description: network-enabled tools for epidemiology and public health practice
- NetEpi, which is short for "Network-enabled Epidemiology", is a
- collaborative project to create a suite of free, open source software
- tools for epidemiology and public health practice. Anyone with an
- interest in population health epidemiology or public health
- informatics is encouraged to examine the prototype tools and to
- consider contributing to their further development. Contributions
- which involve formal and/or informal testing of the tools in a wide
- range of circumstances and environments are particularly welcome, as
- is assistance with design, programming and documentation tasks.
- .
- NetEpi Case Manager is a tool for securely collecting structured
- information about cases and contacts of communicable (and other)
- diseases through Web browsers and the Internet. New data collection
- forms can be designed and deployed quickly by epidemiologists, using
- a "point-and-click" interface, without the need for knowledge of or
- training in any programming language. Data can then be collected from
- users of the system, who can be located anywhere in the world, into a
- centralised database. All that is needed by users of the system is a
- relatively recent Web browser and an Internet connection ("NetEpi" is
- short for "Network-enabled Epidemiology"). In many respects, NetEpi
- Case Manager is like a Web-enabled version of the data entry
- facilities in the very popular Epi Info suite of programmes published
- by the US Centers for Disease Control and Prevention, and in the
- Danish EpiData project, which is available for several languages. The
- software was developed by the Centre for Epidemiology and Research of
- the New South Wales Department of Health, with contributions from
- Population Health Division of the Australian Government Department of
- Health and Ageing.
- .
- The software was developed by New South Wales Department of Health.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 078449d..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,548 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: NetEpi Collection
-Upstream-Contact: http://code.google.com/p/netepi/downloads/list
-Source: http://code.google.com/p/netepi/downloads/list
-
-Files: *
-Copyright: © 2004-2010 Tim CHURCHES <TCHUR at doh.health.nsw.gov.au>
-           Health Administration Corporation
-License: HACOS
-
-Files: debian/*
-Copyright: © 2005-2012 Andreas Tille <tille at debian.org>
-License: LGPL
-
-License: HACOS
- NetEpi Analysis is licensed under the terms of the Health
- Administration Corporation Open Source Licence V1.2 (HACOS License V1.2),
- the complete text of which appears below.
- .
- HEALTH ADMINISTRATION CORPORATION OPEN SOURCE LICENSE VERSION 1.2
- .
- 1. DEFINITIONS. 
- .
-   "Commercial Use" shall mean distribution or otherwise making the
-   Covered Software available to a third party.
- .
-   "Contributor" shall mean each entity that creates or contributes to
-   the creation of Modifications.
- .
-   "Contributor Version" shall mean in case of any Contributor the
-   combination of the Original Software, prior Modifications used by a
-   Contributor, and the Modifications made by that particular Contributor
-   and in case of Health Administration Corporation in addition the
-   Original Software in any form, including the form as Executable.
- .
-   "Covered Software" shall mean the Original Software or Modifications
-   or the combination of the Original Software and Modifications, in
-   each case including portions thereof.
- .
-   "Electronic Distribution Mechanism" shall mean a mechanism generally
-   accepted in the software development community for the electronic
-   transfer of data.
- .
-   "Executable" shall mean Covered Software in any form other than
-   Source Code.
- .
-   "Initial Developer" shall mean the individual or entity identified as
-   the Initial Developer in the Source Code notice required by Exhibit A.
- .
-   "Health Administration Corporation" shall mean the Health
-   Administration Corporation as established by the Health Administration
-   Act 1982, as amended, of the State of New South Wales, Australia. The
-   Health Administration Corporation has its offices at 73 Miller Street,
-   North Sydney, New South Wales 2059, Australia.
- .
-   "Larger Work" shall mean a work, which combines Covered Software or
-   portions thereof with code not governed by the terms of this License.
- .
-   "License" shall mean this document.
- .
-   "Licensable" shall mean having the right to grant, to the maximum
-   extent possible, whether at the time of the initial grant or
-   subsequently acquired, any and all of the rights conveyed herein.
- .
-   "Modifications" shall mean any addition to or deletion from the
-   substance or structure of either the Original Software or any previous
-   Modifications. When Covered Software is released as a series of files,
-   a Modification is:
- .
-   a) Any addition to or deletion from the contents of a file
-      containing Original Software or previous Modifications.
- .
-   b) Any new file that contains any part of the Original Software or
-      previous Modifications.
- .
-   "Original Software" shall mean the Source Code of computer software
-   code which is described in the Source Code notice required by Exhibit
-   A as Original Software, and which, at the time of its release under
-   this License is not already Covered Software governed by this License.
- .
-   "Patent Claims" shall mean any patent claim(s), now owned or hereafter
-   acquired, including without limitation, method, process, and apparatus
-   claims, in any patent Licensable by grantor.
- .
-   "Source Code" shall mean the preferred form of the Covered Software
-   for making modifications to it, including all modules it contains,
-   plus any associated interface definition files, scripts used to
-   control compilation and installation of an Executable, or source
-   code differential comparisons against either the Original Software or
-   another well known, available Covered Software of the Contributor's
-   choice. The Source Code can be in a compressed or archival form,
-   provided the appropriate decompression or de-archiving software is
-   widely available for no charge.
- .
-   "You" (or "Your") shall mean an individual or a legal entity exercising
-   rights under, and complying with all of the terms of, this License or
-   a future version of this License issued under Section 6.1. For legal
-   entities, "You" includes an entity which controls, is controlled
-   by, or is under common control with You. For the purposes of this
-   definition, "control" means (a) the power, direct or indirect,
-   to cause the direction or management of such entity, whether by
-   contract or otherwise, or (b) ownership of more than fifty per cent
-   (50%) of the outstanding shares or beneficial ownership of such entity.
- .
- 2. SOURCE CODE LICENSE. 
- .
- 2.1 Health Administration Corporation Grant. 
- .
- Subject to the terms of this License, Health Administration Corporation
- hereby grants You a world-wide, royalty-free, non-exclusive license,
- subject to third party intellectual property claims:
- .
- a) under copyrights Licensable by Health Administration Corporation
-    to use, reproduce, modify, display, perform, sublicense and
-    distribute the Original Software (or portions thereof) with or without
-    Modifications, and/or as part of a Larger Work;
- .
- b) and under Patents Claims infringed by the making, using or selling
-    of Original Software, to make, have made, use, practice, sell, and
-    offer for sale, and/or otherwise dispose of the Original Software
-    (or portions thereof).
- .
- c) The licenses granted in this Section 2.1(a) and (b) are effective
-    on the date Health Administration Corporation first distributes
-    Original Software under the terms of this License.
- .
- d) Notwithstanding Section 2.1(b) above, no patent license is granted:
-    1) for code that You delete from the Original Software; 2) separate
-    from the Original Software; or 3) for infringements caused by: i)
-    the modification of the Original Software or ii) the combination of
-    the Original Software with other software or devices.
- .
- 2.2 Contributor Grant. 
- .
- Subject to the terms of this License and subject to third party
- intellectual property claims, each Contributor hereby grants You a
- world-wide, royalty-free, non-exclusive license:
- .
- a) under copyrights Licensable by Contributor, to use, reproduce,
-    modify, display, perform, sublicense and distribute the Modifications
-    created by such Contributor (or portions thereof) either on an
-    unmodified basis, with other Modifications, as Covered Software and/or
-    as part of a Larger Work; and
- .
- b) under Patent Claims necessarily infringed by the making, using,
-    or selling of Modifications made by that Contributor either alone
-    and/or in combination with its Contributor Version (or portions of
-    such combination), to make, use, sell, offer for sale, have made,
-    and/or otherwise dispose of: 1) Modifications made by that Contributor
-    (or portions thereof); and 2) the combination of Modifications made
-    by that Contributor with its Contributor Version (or portions of
-    such combination).
- .
- c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective
-    on the date Contributor first makes Commercial Use of the Covered
-    Software.
- .
- d) Notwithstanding Section 2.2(b) above, no patent license is granted:
-    1) for any code that Contributor has deleted from the Contributor
-    Version; 2) separate from the Contributor Version; 3) for infringements
-    caused by: i) third party modifications of Contributor Version or ii)
-    the combination of Modifications made by that Contributor with other
-    software (except as part of the Contributor Version) or other devices;
-    or 4) under Patent Claims infringed by Covered Software in the absence
-    of Modifications made by that Contributor.
- .
- 3. DISTRIBUTION OBLIGATIONS. 
- .
- 3.1 Application of License. 
- .
- The Modifications which You create or to which You contribute are governed
- by the terms of this License, including without limitation Section
- 2.2. The Source Code version of Covered Software may be distributed
- only under the terms of this License or a future version of this License
- released under Section 6.1, and You must include a copy of this License
- with every copy of the Source Code You distribute. You may not offer or
- impose any terms on any Source Code version that alters or restricts the
- applicable version of this License or the recipients' rights hereunder.
- .
- 3.2 Availability of Source Code. 
- .
- Any Modification which You create or to which You contribute must be made
- available in Source Code form under the terms of this License either on
- the same media as an Executable version or via an accepted Electronic
- Distribution Mechanism to anyone to whom you made an Executable version
- available; and if made available via Electronic Distribution Mechanism,
- must remain available for at least twelve (12) months after the date it
- initially became available, or at least six (6) months after a subsequent
- version of that particular Modification has been made available to
- such recipients. You are responsible for ensuring that the Source Code
- version remains available even if the Electronic Distribution Mechanism
- is maintained by a third party.
- .
- 3.3 Description of Modifications. 
- .
- You must cause all Covered Software to which You contribute to contain
- a file documenting the changes You made to create that Covered Software
- and the date of any change. You must include a prominent statement that
- the Modification is derived, directly or indirectly, from Original
- Software provided by Health Administration Corporation and including
- the name of Health Administration Corporation in (a) the Source Code,
- and (b) in any notice in an Executable version or related documentation
- in which You describe the origin or ownership of the Covered Software.
- .
- 3.4 Intellectual Property Matters 
- .
- a) Third Party Claims. 
- .
-   If Contributor has knowledge that a license under a third party's
-   intellectual property rights is required to exercise the rights
-   granted by such Contributor under Sections 2.1 or 2.2, Contributor
-   must include a text file with the Source Code distribution titled
-   "LEGAL'' which describes the claim and the party making the claim
-   in sufficient detail that a recipient will know whom to contact. If
-   Contributor obtains such knowledge after the Modification is made
-   available as described in Section 3.2, Contributor shall promptly
-   modify the LEGAL file in all copies Contributor makes available
-   thereafter and shall take other steps (such as notifying appropriate
-   mailing lists or newsgroups) reasonably calculated to inform those
-   who received the Covered Software that new knowledge has been obtained.
- .
- b) Contributor APIs. 
- .
-   If Contributor's Modifications include an application programming
-   interface (API) and Contributor has knowledge of patent licenses
-   which are reasonably necessary to implement that API, Contributor
-   must also include this information in the LEGAL file.
- .
- c) Representations. 
- .
-   Contributor represents that, except as disclosed pursuant to Section
-   3.4(a) above, Contributor believes that Contributor's Modifications are
-   Contributor's original creation(s) and/or Contributor has sufficient
-   rights to grant the rights conveyed by this License.
- .
- 3.5 Required Notices. 
- .
- You must duplicate the notice in Exhibit A in each file of the Source
- Code. If it is not possible to put such notice in a particular Source
- Code file due to its structure, then You must include such notice in a
- location (such as a relevant directory) where a user would be likely to
- look for such a notice. If You created one or more Modification(s) You
- may add your name as a Contributor to the notice described in Exhibit
- A. You must also duplicate this License in any documentation for the
- Source Code where You describe recipients' rights or ownership rights
- relating to Covered Software. You may choose to offer, and to charge a
- fee for, warranty, support, indemnity or liability obligations to one or
- more recipients of Covered Software. However, You may do so only on Your
- own behalf, and not on behalf of Health Administration Corporation or any
- Contributor. You must make it absolutely clear that any such warranty,
- support, indemnity or liability obligation is offered by You alone,
- and You hereby agree to indemnify Health Administration Corporation and
- every Contributor for any liability incurred by Health Administration
- Corporation or such Contributor as a result of warranty, support,
- indemnity or liability terms You offer.
- .
- 3.6 Distribution of Executable Versions. 
- .
- You may distribute Covered Software in Executable form only if the
- requirements of Sections 3.1-3.5 have been met for that Covered Software,
- and if You include a notice stating that the Source Code version of the
- Covered Software is available under the terms of this License, including
- a description of how and where You have fulfilled the obligations of
- Section 3.2. The notice must be conspicuously included in any notice in
- an Executable version, related documentation or collateral in which You
- describe recipients' rights relating to the Covered Software. You may
- distribute the Executable version of Covered Software or ownership rights
- under a license of Your choice, which may contain terms different from
- this License, provided that You are in compliance with the terms of this
- License and that the license for the Executable version does not attempt
- to limit or alter the recipient's rights in the Source Code version from
- the rights set forth in this License. If You distribute the Executable
- version under a different license You must make it absolutely clear
- that any terms which differ from this License are offered by You alone,
- not by Health Administration Corporation or any Contributor. You hereby
- agree to indemnify Health Administration Corporation and every Contributor
- for any liability incurred by Health Administration Corporation or such
- Contributor as a result of any such terms You offer.
- .
- 3.7 Larger Works. 
- .
- You may create a Larger Work by combining Covered Software with other
- software not governed by the terms of this License and distribute the
- Larger Work as a single product. In such a case, You must make sure the
- requirements of this License are fulfilled for the Covered Software.
- .
- 4. INABILITY TO COMPLY DUE TO STATUTE OR REGULATION. 
- .
- If it is impossible for You to comply with any of the terms of this
- License with respect to some or all of the Covered Software due to
- statute, judicial order, or regulation then You must: (a) comply with the
- terms of this License to the maximum extent possible; and (b) describe the
- limitations and the code they affect. Such description must be included
- in the LEGAL file described in Section 3.4 and must be included with all
- distributions of the Source Code.  Except to the extent prohibited by
- statute or regulation, such description must be sufficiently detailed
- for a recipient of ordinary skill to be able to understand it.
- .
- 5. APPLICATION OF THIS LICENSE. 
- .
- This License applies to code to which Health Administration Corporation
- has attached the notice in Exhibit A and to related Covered Software.
- .
- 6. VERSIONS OF THE LICENSE. 
- .
- 6.1 New Versions. 
- .
- Health Administration Corporation may publish revised and/or new
- versions of the License from time to time. Each version will be given
- a distinguishing version number.
- .
- 6.2 Effect of New Versions. 
- .
- Once Covered Software has been published under a particular version
- of the License, You may always continue to use it under the terms of
- that version. You may also choose to use such Covered Software under
- the terms of any subsequent version of the License published by Health
- Administration Corporation. No one other than Health Administration
- Corporation has the right to modify the terms applicable to Covered
- Software created under this License.
- .
- 7. DISCLAIMER OF WARRANTY. 
- .
- COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS'' BASIS,
- WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
- WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
- DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE
- ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS
- WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU
- (NOT HEALTH ADMINISTRATION CORPORATION, ITS LICENSORS OR AFFILIATES OR
- ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR
- OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
- OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER
- EXCEPT UNDER THIS DISCLAIMER.
- .
- 8. TERMINATION. 
- .
- 8.1 This License and the rights granted hereunder will terminate
- automatically if You fail to comply with terms herein and fail to
- cure such breach within 30 days of becoming aware of the breach. All
- sublicenses to the Covered Software which are properly granted shall
- survive any termination of this License. Provisions which, by their
- nature, must remain in effect beyond the termination of this License
- shall survive.
- .
- 8.2 If You initiate litigation by asserting a patent infringement claim
- (excluding declatory judgment actions) against Health Administration
- Corporation or a Contributor (Health Administration Corporation
- or Contributor against whom You file such action is referred to as
- "Participant") alleging that:
- .
- a) such Participant's Contributor Version directly or indirectly
-    infringes any patent, then any and all rights granted by such
-    Participant to You under Sections 2.1 and/or 2.2 of this License
-    shall, upon 60 days notice from Participant terminate prospectively,
-    unless if within 60 days after receipt of notice You either: (i)
-    agree in writing to pay Participant a mutually agreeable reasonable
-    royalty for Your past and future use of Modifications made by such
-    Participant, or (ii) withdraw Your litigation claim with respect to
-    the Contributor Version against such Participant. If within 60 days
-    of notice, a reasonable royalty and payment arrangement are not
-    mutually agreed upon in writing by the parties or the litigation
-    claim is not withdrawn, the rights granted by Participant to
-    You under Sections 2.1 and/or 2.2 automatically terminate at the
-    expiration of the 60 day notice period specified above.
- .
- b) any software, hardware, or device, other than such Participant's
-    Contributor Version, directly or indirectly infringes any patent,
-    then any rights granted to You by such Participant under Sections
-    2.1(b) and 2.2(b) are revoked effective as of the date You first
-    made, used, sold, distributed, or had made, Modifications made by
-    that Participant.
- .
- 8.3 If You assert a patent infringement claim against Participant
- alleging that such Participant's Contributor Version directly or
- indirectly infringes any patent where such claim is resolved (such as by
- license or settlement) prior to the initiation of patent infringement
- litigation, then the reasonable value of the licenses granted by such
- Participant under Sections 2.1 or 2.2 shall be taken into account in
- determining the amount or value of any payment or license.
- .
- 8.4 In the event of termination under Sections 8.1 or 8.2 above, all
- end user license agreements (excluding distributors and resellers) which
- have been validly granted by You or any distributor hereunder prior to
- termination shall survive termination.
- .
- 9. LIMITATION OF LIABILITY. 
- .
- 9.1 UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
- (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, HEALTH
- ADMINISTRATION CORPORATION, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR
- OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE
- TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
- DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
- OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
- ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE
- BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
- LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
- RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
- PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
- OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, BUT MAY ALLOW
- LIABILITY TO BE LIMITED; IN SUCH CASES, A PARTY'S, ITS EMPLOYEES',
- LICENSORS' OR AFFILIATES' LIABILITY SHALL BE LIMITED TO AUD$100. NOTHING
- CONTAINED IN THIS LICENSE SHALL PREJUDICE THE STATUTORY RIGHTS OF ANY
- PARTY DEALING AS A CONSUMER.
- .
- 9.2 Notwithstanding any other clause in the licence, and to the extent
- permitted by law:
- .
- (a) Health Administration Corporation ("the Corporation") excludes all
-     conditions and warranties which would otherwise be implied into
-     a supply of goods or services arising out of or in relation to
-     the granting of this licence by the Corporation or any associated
-     acquisition of software to which this licence relates;
- .
- (b) Where a condition or warranty is implied into such a supply and
-     that condition or warranty cannot be excluded by law that warranty
-     or condition is implied into that supply and the liability of the
-     Health Administration Corporation for a breach of that condition or
-     warranty is limited to the fullest extent permitted by law and, in
-     respect of conditions and warranties implied by the Trade Practices
-     Act (Commonwealth of Australia) 1974, is limited, to the extent
-     permitted by law, to one or more of the following at the election
-     of the Corporation:
- .
-    (A) In the case of goods: (i) the replacement of the goods or the
-        supply of equivalent goods; (ii) the repair of the goods; (iii)
-        the payment of the cost of replacing the goods or of acquiring
-        equivalent goods; (iv) the payment of the cost of having the
-        goods repaired; and
- .
-    (B) in the case of services: (i) the supplying of the services again;
-        or (ii) the payment of the cost of having the services supplied
-        again.
- .
- 10. MISCELLANEOUS. 
- .
- This License represents the complete agreement concerning subject matter
- hereof. All rights in the Covered Software not expressly granted under
- this License are reserved. Nothing in this License shall grant You any
- rights to use any of the trademarks of Health Administration Corporation
- or any of its Affiliates, even if any of such trademarks are included
- in any part of Covered Software and/or documentation to it.
- .
- This License is governed by the laws of the State of New South Wales,
- Australia excluding its conflict-of-law provisions. All disputes or
- litigation arising from or relating to this Agreement shall be subject
- to the jurisdiction of the Supreme Court of New South Wales. If any part
- of this Agreement is found void and unenforceable, it will not affect
- the validity of the balance of the Agreement, which shall remain valid
- and enforceable according to its terms.
- .
- 11. RESPONSIBILITY FOR CLAIMS. 
- .
- As between Health Administration Corporation and the Contributors,
- each party is responsible for claims and damages arising, directly or
- indirectly, out of its utilisation of rights under this License and You
- agree to work with Health Administration Corporation and Contributors
- to distribute such responsibility on an equitable basis. Nothing herein
- is intended or shall be deemed to constitute any admission of liability.
- .
- EXHIBIT A 
- .
- The contents of this file are subject to the HACOS License Version 1.2
- (the "License"); you may not use this file except in compliance with
- the License.
- .
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
- License for the specific language governing rights and limitations under
- the License.
- .
- The Original Software is "NetEpi Analysis". The Initial Developer
- of the Original Software is the Health Administration Corporation,
- incorporated in the State of New South Wales, Australia.
- .
- APPENDIX 1. DIFFERENCES BETWEEN THE HACOS LICENSE VERSION 1.2, THE
- MOZILLA PUBLIC LICENSE VERSION 1.1 AND THE NOKIA OPEN SOURCE LICENSE
- (NOKOS LICENSE) VERSION 1.0A
- .
- The HACOS License Version 1.2 was derived from the Mozilla Public
- License Version 1.1 using some of the changes to the Mozilla Public
- License embodied in the Nokia Open Source License (NOKOS License)
- Version 1.0a. The differences between the HACOS License Version 1.2
- (this document), the Mozilla Public License and the NOKOS License are
- as follows:
- .
- i.    The title of the license was changed to "Health Administration
-       Corporation Open Source License Version 1.2".
- .
- ii.   Globally, all references to "Netscape Communications Corporation",
-       "Mozilla", "Nokia" and "Nokia Corporation" were changed to "Health
-       Administration Corporation".
- .
- iii.  Globally, the words "means", "Covered Code" and "Covered Software"
-       as used in the Mozilla Public License were changed to "shall means",
-       "Covered Code" and "Covered Software" respectively, as used in
-       the NOKOS License.
- .
- iv.   In Section 1 (Definitions), a definition of "Health Administration
-       Corporation" was added.
- .
- v.    In Section 2, the term "intellectual property rights" used in the
-       Mozilla Public License was replaced by the term "copyrights"
-       as used in the NOKOS License.
- .
- vi.   In Section 2.2 (Contributor Grant), the words "Subject to the
-       terms of this License" which appear in the NOKOS License were
-       added to the Mozilla Public License.
- .
- vii.  The sentence "However, You may include an additional document
-       offering the additional rights described in Section 3.5." which
-       appears in the Mozilla Public License was omitted.
- .
- viii. Section 6.3 (Derivative Works) of the Mozilla Public License,
-       which permits modifications to the Mozilla Public License,
-       was omitted.
- .
- ix.   The original Section 9 (Limitation of Liability) was renumbered
-       as Section 9.1, a maximum liability of AUD$100 was specified
-       for those jurisdictions which do not allow complete exclusion of
-       liability but which do allow limitation of liability. The sentence
-       "NOTHING CONTAINED IN THE LICENSE SHALL PREJUDICE THE STATUTORY
-       RIGHTS OF ANY PARTY DEALING AS A CONSUMER.", which appears in the
-       NOKOS License but not in the Mozilla Public License, was added.
- .
- x.    Section 9.2 was added in order to further limit liability to the
-       maximum extent permitted by the Commonwealth of Australia Trade
-       Practices Act 1974.
- .
- xi.   Section 10 of the Mozilla Public License, which provides additional
-       conditions for United States Government End Users, was omitted.
- .
- xii.  The governing law and jurisdiction for the settlement of disputes
-       in Section 11 of the Mozilla Public License and Section 10 of the
-       NOKOS License was changed to the laws of the State of New South
-       Wales and the Supreme Court of New South Wales respectively. The
-       exclusion of the application of the United Nations Convention on
-       Contracts for the International Sale of Goods which appears in
-       the Mozilla Public License was omitted.
- .
- xiii. Section 13 (Multiple-Licensed Code) of the Mozilla Public License
-       was omitted.
- .
- xiv.  The provisions for alternative licensing arrangement for contributed
-       code which appear in Exhibit A of the Mozilla Public License
-       were omitted.
-
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index ad5edab..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/make -f
-
-include /usr/share/cdbs/1/rules/debhelper.mk
-
-install/netepi-collection::
-	python install.py \
-	        appname=collection \
-	        dsn='::collection:' \
-	        install_prefix=$(DEB_DESTDIR) \
-	        create_db=False
-
-get-orig-source:
-	-uscan --upstream-version 0 --rename
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 921a976..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=4
-
-https://github.com/timchurches/NetEpi-Collection/releases .*/archive/(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
diff --git a/doc/Collection-ER.dia b/doc/Collection-ER.dia
new file mode 100644
index 0000000..7c96ba7
--- /dev/null
+++ b/doc/Collection-ER.dia
@@ -0,0 +1,11871 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<dia:diagram xmlns:dia="http://www.lysator.liu.se/~alla/dia/">
+  <dia:diagramdata>
+    <dia:attribute name="background">
+      <dia:color val="#ffffff"/>
+    </dia:attribute>
+    <dia:attribute name="pagebreak">
+      <dia:color val="#000099"/>
+    </dia:attribute>
+    <dia:attribute name="paper">
+      <dia:composite type="paper">
+        <dia:attribute name="name">
+          <dia:string>#A4#</dia:string>
+        </dia:attribute>
+        <dia:attribute name="tmargin">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="bmargin">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="lmargin">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="rmargin">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="is_portrait">
+          <dia:boolean val="false"/>
+        </dia:attribute>
+        <dia:attribute name="scaling">
+          <dia:real val="0.24516129493713379"/>
+        </dia:attribute>
+        <dia:attribute name="fitto">
+          <dia:boolean val="true"/>
+        </dia:attribute>
+        <dia:attribute name="fitwidth">
+          <dia:int val="1"/>
+        </dia:attribute>
+        <dia:attribute name="fitheight">
+          <dia:int val="1"/>
+        </dia:attribute>
+      </dia:composite>
+    </dia:attribute>
+    <dia:attribute name="grid">
+      <dia:composite type="grid">
+        <dia:attribute name="width_x">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="width_y">
+          <dia:real val="1"/>
+        </dia:attribute>
+        <dia:attribute name="visible_x">
+          <dia:int val="1"/>
+        </dia:attribute>
+        <dia:attribute name="visible_y">
+          <dia:int val="1"/>
+        </dia:attribute>
+        <dia:composite type="color"/>
+      </dia:composite>
+    </dia:attribute>
+    <dia:attribute name="color">
+      <dia:color val="#7f7f7f"/>
+    </dia:attribute>
+    <dia:attribute name="guides">
+      <dia:composite type="guides">
+        <dia:attribute name="hguides"/>
+        <dia:attribute name="vguides"/>
+      </dia:composite>
+    </dia:attribute>
+  </dia:diagramdata>
+  <dia:layer name="Background" visible="true" active="true">
+    <dia:object type="UML - Class" version="0" id="O0">
+      <dia:attribute name="obj_pos">
+        <dia:point val="96,3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="95.95,2.95;106.175,11.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="96,3"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.125"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="8"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#syndrome_types#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#priority#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#description: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#additional_info#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#enabled: BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#post_date: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#expiry_date: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O1">
+      <dia:attribute name="obj_pos">
+        <dia:point val="96,12"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="95.95,11.95;106.945,18.45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="96,12"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.895"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="6.4000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#forms#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label: VARCHAR PRIMARY KEY#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_type: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#cur_version: INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#allow_multiple: BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#def_update_time#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O2">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,2.95;88.565,7.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,3"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#syndrome_forms#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_forms_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id: REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_label: REFERENCES forms#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O3">
+      <dia:attribute name="obj_pos">
+        <dia:point val="96,34"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="95.95,33.95;107.715,63.65"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="96,34"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="11.664999999999999"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="29.600000000000016"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#persons#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#person_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#last_update#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#data_src#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#surname: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#given_names: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#DOB: DATESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#DOB_prec#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#sex: VARCHAR(1)#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#interpreter_req#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#home_phone: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_phone: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#mobile_phone#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#fax_phone#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#e_mail#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#street_address: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#locality: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#state: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#postcode: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#country#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt_street_address#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt_locality#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt_state#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt_postcode#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt_country#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_street_address#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_locality#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_state#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_postcode#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#work_country#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#occupation#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#passport_number#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#passport_country#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#passport_number_2#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#passport_country_2#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#indigenous_status#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O4">
+      <dia:attribute name="obj_pos">
+        <dia:point val="33,12"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="32.95,11.95;43.945,16.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="33,12"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.895"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#groups#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_name: VARCHAR UNIQUE#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#rights: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#description: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O5">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,8.95;21.33,13.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,9"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="11.279999999999999"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#unit_groups#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_groups_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id: REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_id: REFERENCES groups#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O6">
+      <dia:attribute name="obj_pos">
+        <dia:point val="33,19"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="32.95,18.95;46.64,26.25"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="33,19"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="13.59"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="7.1999999999999993"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#units#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#enabled: BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#rights#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#street_address: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#postal_address: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_user_id: REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O7">
+      <dia:attribute name="obj_pos">
+        <dia:point val="33,28"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="32.95,27.95;47.795,48.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="33,28"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="14.744999999999999"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="20.000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#users#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#last_update#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#enabled: BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#deleted#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#rights#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#username: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#fullname: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#title#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#agency#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#expertise#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#password: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#creation_timestamp: DATESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#email: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#phone_home: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#phone_work: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#phone_mobile: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#phone_fax: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#bad_attempts: INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#bad_timestamp: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#preferences#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BINARY#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#privacy#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#sponsoring_user_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#enable_key#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O8">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,15"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,14.95;20.56,19.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,15"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#unit_users#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_user_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id: REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id: REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O9">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,37"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,36.95;88.565,50.65"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,37"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="13.600000000000003"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#cases#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#last_update#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#deleted#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#delete_reason#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#delete_timestamp#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#local_case_id: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#person_id: REFERENCES persons#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id: REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_status: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#notification_datetime: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#onset_datetime: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#notifier_name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#notifier_contact#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#notes#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_assignment#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O10">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,21"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,20.95;20.56,25.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,21"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#case_acl#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_acl_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id: REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O11">
+      <dia:attribute name="obj_pos">
+        <dia:point val="56,40"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="55.95,39.95;67.715,49.65"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="56,40"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="11.664999999999999"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="9.5999999999999996"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#case_form_summary#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#summary_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id: REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_label: REFERENCES forms#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_version: INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_date: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#summary: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#data_src#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#deleted#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#delete_reason#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#delete_timestamp#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O12">
+      <dia:attribute name="obj_pos">
+        <dia:point val="33,3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="32.95,2.95;42.405,9.45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="33,3"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="9.3550000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="6.4000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#bulletins#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#bulletin_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#post_date: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#expiry_date: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#title: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#synopsys: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#detail: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O13">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,2.95;23.64,7.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,3"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="13.59"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#group_bulletins#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_bulletins_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#bulletin_id: REFERENCES bulletins#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_id: REFERENCES groups#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O14">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,17.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,17.2191;33.05,20.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,17.7"/>
+        <dia:point val="29,17.7"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O8" connection="11"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O15">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,18.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,18.0191;33.05,29.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,18.5"/>
+        <dia:point val="28,18.5"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O8" connection="13"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O16">
+      <dia:attribute name="obj_pos">
+        <dia:point val="46.59,25.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,25.2191;49.05,29.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="46.59,25.7"/>
+        <dia:point val="49,25.7"/>
+        <dia:point val="49,27"/>
+        <dia:point val="28,27"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O6" connection="21"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O17">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,23.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,20.85;33.05,24.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,23.7"/>
+        <dia:point val="29,23.7"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O10" connection="11"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O18">
+      <dia:attribute name="obj_pos">
+        <dia:point val="21.28,11.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="21.2129,11.2191;33.05,20.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="21.28,11.7"/>
+        <dia:point val="29,11.7"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O5" connection="11"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O19">
+      <dia:attribute name="obj_pos">
+        <dia:point val="21.28,12.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="21.2129,12.0191;33.05,13.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="21.28,12.5"/>
+        <dia:point val="30,12.5"/>
+        <dia:point val="30,13.9"/>
+        <dia:point val="33,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#3388aa"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O5" connection="13"/>
+        <dia:connection handle="1" to="O4" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O20">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,43.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,35.85;96.05,44.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,43.7"/>
+        <dia:point val="94,43.7"/>
+        <dia:point val="94,35.9"/>
+        <dia:point val="96,35.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#886611"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O9" connection="21"/>
+        <dia:connection handle="1" to="O3" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O21">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,44.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,4.85;96.05,44.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,44.5"/>
+        <dia:point val="92,44.5"/>
+        <dia:point val="92,38"/>
+        <dia:point val="92,38"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O9" connection="23"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O22">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,5.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,4.85;96.05,6.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,5.7"/>
+        <dia:point val="92,5.7"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O2" connection="11"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O23">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,6.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,6.0191;96.05,13.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,6.5"/>
+        <dia:point val="91,6.5"/>
+        <dia:point val="91,13.9"/>
+        <dia:point val="96,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#ff8888"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O2" connection="13"/>
+        <dia:connection handle="1" to="O1" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O24">
+      <dia:attribute name="obj_pos">
+        <dia:point val="67.665,42.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="67.5979,38.85;73.05,43.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="67.665,42.7"/>
+        <dia:point val="71,42.7"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O11" connection="11"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O25">
+      <dia:attribute name="obj_pos">
+        <dia:point val="67.665,43.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="67.5979,13.85;96.05,43.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="67.665,43.5"/>
+        <dia:point val="70,43.5"/>
+        <dia:point val="70,20"/>
+        <dia:point val="91,20"/>
+        <dia:point val="91,13.9"/>
+        <dia:point val="96,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#ff8888"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O11" connection="13"/>
+        <dia:connection handle="1" to="O1" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O26">
+      <dia:attribute name="obj_pos">
+        <dia:point val="23.59,5.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="23.5229,4.85;33.05,6.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="23.59,5.7"/>
+        <dia:point val="32,5.7"/>
+        <dia:point val="32,4.9"/>
+        <dia:point val="33,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O13" connection="11"/>
+        <dia:connection handle="1" to="O12" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O27">
+      <dia:attribute name="obj_pos">
+        <dia:point val="23.59,6.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="23.5229,6.0191;33.05,13.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="23.59,6.5"/>
+        <dia:point val="30,6.5"/>
+        <dia:point val="30,13.9"/>
+        <dia:point val="33,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#3388aa"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O13" connection="13"/>
+        <dia:connection handle="1" to="O4" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O28">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,45"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,44.95;25.565,49.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#group_syndromes#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_syndromes_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id: REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#group_id: REFERENCES groups#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O29">
+      <dia:attribute name="obj_pos">
+        <dia:point val="25.515,47.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="25.4479,4.85;96.05,52.05"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="25.515,47.7"/>
+        <dia:point val="27,47.7"/>
+        <dia:point val="27,52"/>
+        <dia:point val="92,52"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O28" connection="11"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O30">
+      <dia:attribute name="obj_pos">
+        <dia:point val="25.515,48.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="25.4479,13.85;33.05,48.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="25.515,48.5"/>
+        <dia:point val="30,48.5"/>
+        <dia:point val="30,13.9"/>
+        <dia:point val="33,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#3388aa"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O28" connection="13"/>
+        <dia:connection handle="1" to="O4" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O31">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,27"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,26.95;20.945,33.45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,27"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.895"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="6.4000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#admin_log#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#admin_log_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id: REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#event_timestamp: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#event_type: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#remote_addr: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#forwarded_addr: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O32">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,36"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,35.95;20.945,43.25"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,36"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.895"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="7.1999999999999993"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#user_log#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_log_id: SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id: REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#event_timestamp: TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#event_type: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#remote_addr: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#forwarded_addr: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id: REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O33">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,54"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,53.95;85.1,57.25"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,54"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="12.050000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="3.2000000000000002"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#person_phonetics#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#person_id: REFERENCES persons#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#phonetics: VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O34">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.895,29.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.8279,29.2191;33.05,30.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.895,29.7"/>
+        <dia:point val="28,29.7"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O31" connection="11"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O35">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.895,38.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.8279,29.85;33.05,39.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.895,38.7"/>
+        <dia:point val="28,38.7"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O32" connection="11"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O36">
+      <dia:attribute name="obj_pos">
+        <dia:point val="85.05,55.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="84.9829,35.85;96.05,56.3809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="85.05,55.9"/>
+        <dia:point val="94,55.9"/>
+        <dia:point val="94,35.9"/>
+        <dia:point val="96,35.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#886611"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O33" connection="9"/>
+        <dia:connection handle="1" to="O3" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O37">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.895,42.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.8279,38.85;73.05,51.05"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.895,42.7"/>
+        <dia:point val="32,42.7"/>
+        <dia:point val="32,51"/>
+        <dia:point val="71,51"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O32" connection="21"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O38">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,27"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,26.95;88.565,35.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,27"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="8.7999999999999989"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#syndrome_demog_fields#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#synddf_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#show_case#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#show_form#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#show_search#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#show_person#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#show_result#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O39">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,8"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,7.95;88.565,12.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,8"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#syndrome_case_status#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndcs_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O40">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,15"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,14.95;80.095,19.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,15"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="7.0449999999999999"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#form_defs#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#version#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#xmldef#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O41">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,10.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,4.85;96.05,11.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,10.7"/>
+        <dia:point val="92,10.7"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O39" connection="11"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O42">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,29.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,4.85;96.05,30.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,29.7"/>
+        <dia:point val="92,29.7"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O38" connection="11"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O43">
+      <dia:attribute name="obj_pos">
+        <dia:point val="80.045,16.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="79.9779,13.85;96.05,17.3809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="80.045,16.9"/>
+        <dia:point val="91,16.9"/>
+        <dia:point val="91,13.9"/>
+        <dia:point val="96,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#ff8888"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O40" connection="9"/>
+        <dia:connection handle="1" to="O1" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:group>
+      <dia:object type="Standard - Box" version="0" id="O44">
+        <dia:attribute name="obj_pos">
+          <dia:point val="96,19"/>
+        </dia:attribute>
+        <dia:attribute name="obj_bb">
+          <dia:rectangle val="95.95,18.95;113.05,22.2"/>
+        </dia:attribute>
+        <dia:attribute name="elem_corner">
+          <dia:point val="96,19"/>
+        </dia:attribute>
+        <dia:attribute name="elem_width">
+          <dia:real val="17"/>
+        </dia:attribute>
+        <dia:attribute name="elem_height">
+          <dia:real val="3.1499999999999915"/>
+        </dia:attribute>
+        <dia:attribute name="show_background">
+          <dia:boolean val="true"/>
+        </dia:attribute>
+      </dia:object>
+      <dia:object type="Standard - Text" version="1" id="O45">
+        <dia:attribute name="obj_pos">
+          <dia:point val="96,20.15"/>
+        </dia:attribute>
+        <dia:attribute name="obj_bb">
+          <dia:rectangle val="96,19.2575;114.047,20.3775"/>
+        </dia:attribute>
+        <dia:attribute name="text">
+          <dia:composite type="text">
+            <dia:attribute name="string">
+              <dia:string># form_<form_label>_<form_version>#</dia:string>
+            </dia:attribute>
+            <dia:attribute name="font">
+              <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+            </dia:attribute>
+            <dia:attribute name="height">
+              <dia:real val="1.2"/>
+            </dia:attribute>
+            <dia:attribute name="pos">
+              <dia:point val="96,20.15"/>
+            </dia:attribute>
+            <dia:attribute name="color">
+              <dia:color val="#000000"/>
+            </dia:attribute>
+            <dia:attribute name="alignment">
+              <dia:enum val="0"/>
+            </dia:attribute>
+          </dia:composite>
+        </dia:attribute>
+        <dia:attribute name="valign">
+          <dia:enum val="3"/>
+        </dia:attribute>
+      </dia:object>
+      <dia:object type="Standard - Text" version="1" id="O46">
+        <dia:attribute name="obj_pos">
+          <dia:point val="96,21.15"/>
+        </dia:attribute>
+        <dia:attribute name="obj_bb">
+          <dia:rectangle val="96,20.555;110.51,22.1025"/>
+        </dia:attribute>
+        <dia:attribute name="text">
+          <dia:composite type="text">
+            <dia:attribute name="string">
+              <dia:string># A form instance table exists for each version
+ of each form listed in the "forms" table.#</dia:string>
+            </dia:attribute>
+            <dia:attribute name="font">
+              <dia:font family="sans" style="0" name="Helvetica"/>
+            </dia:attribute>
+            <dia:attribute name="height">
+              <dia:real val="0.80000000000000004"/>
+            </dia:attribute>
+            <dia:attribute name="pos">
+              <dia:point val="96,21.15"/>
+            </dia:attribute>
+            <dia:attribute name="color">
+              <dia:color val="#000000"/>
+            </dia:attribute>
+            <dia:attribute name="alignment">
+              <dia:enum val="0"/>
+            </dia:attribute>
+          </dia:composite>
+        </dia:attribute>
+        <dia:attribute name="valign">
+          <dia:enum val="3"/>
+        </dia:attribute>
+      </dia:object>
+      <dia:object type="Standard - Box" version="0" id="O47">
+        <dia:attribute name="obj_pos">
+          <dia:point val="96,22.15"/>
+        </dia:attribute>
+        <dia:attribute name="obj_bb">
+          <dia:rectangle val="95.95,22.1;113.05,25.05"/>
+        </dia:attribute>
+        <dia:attribute name="elem_corner">
+          <dia:point val="96,22.15"/>
+        </dia:attribute>
+        <dia:attribute name="elem_width">
+          <dia:real val="17"/>
+        </dia:attribute>
+        <dia:attribute name="elem_height">
+          <dia:real val="2.8499999999999943"/>
+        </dia:attribute>
+        <dia:attribute name="show_background">
+          <dia:boolean val="true"/>
+        </dia:attribute>
+      </dia:object>
+      <dia:object type="Standard - Text" version="1" id="O48">
+        <dia:attribute name="obj_pos">
+          <dia:point val="96,23.15"/>
+        </dia:attribute>
+        <dia:attribute name="obj_bb">
+          <dia:rectangle val="96,22.555;111.915,24.9025"/>
+        </dia:attribute>
+        <dia:attribute name="text">
+          <dia:composite type="text">
+            <dia:attribute name="string">
+              <dia:string>#+summary_id: REFERENCES case_form_summary
++form_date: TIMESTAMP
++[...form fields...]#</dia:string>
+            </dia:attribute>
+            <dia:attribute name="font">
+              <dia:font family="sans" style="0" name="Helvetica"/>
+            </dia:attribute>
+            <dia:attribute name="height">
+              <dia:real val="0.80000000000000004"/>
+            </dia:attribute>
+            <dia:attribute name="pos">
+              <dia:point val="96,23.15"/>
+            </dia:attribute>
+            <dia:attribute name="color">
+              <dia:color val="#000000"/>
+            </dia:attribute>
+            <dia:attribute name="alignment">
+              <dia:enum val="0"/>
+            </dia:attribute>
+          </dia:composite>
+        </dia:attribute>
+        <dia:attribute name="valign">
+          <dia:enum val="3"/>
+        </dia:attribute>
+      </dia:object>
+    </dia:group>
+    <dia:object type="UML - Class" version="0" id="O49">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,61"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,60.95;20.56,66.65"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,61"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="5.5999999999999996"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#workqueues#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#queue_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#description#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O50">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,68"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,67.95;22.87,72.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,68"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="12.82"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#workqueue_members#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#wqm_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#queue_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES workqueues#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O51">
+      <dia:attribute name="obj_pos">
+        <dia:point val="33,58"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="32.95,57.95;49.335,74.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="33,58"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="16.285"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="16.800000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#tasks#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#task_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#parent_task_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES tasks#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#queue_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES workqueues#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#action#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#INTEGER#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#active_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#due_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#completed_by_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#completed_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#locked_by_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#locked_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#task_description#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#annotation#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#form_name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES forms#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#summary_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES case_form_summary#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#assigner_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#assigner_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#originator_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#creation_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O52">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,51"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,50.95;25.565,59.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="8"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#report_params#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#report_params_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#type#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#xmldef#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#STRING#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#sharing#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O53">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,59"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,58.95;87.025,65.45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,59"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="13.975"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="6.4000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#dupe_persons#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#low_person_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES persons#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#high_person_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES persons#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#status#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#CHAR(1)#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#confidence#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#FLOAT#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#exclude_reason#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#timechecked#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O54">
+      <dia:attribute name="obj_pos">
+        <dia:point val="86.975,60.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="86.9079,35.85;96.05,61.3809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="86.975,60.9"/>
+        <dia:point val="94,60.9"/>
+        <dia:point val="94,35.9"/>
+        <dia:point val="96,35.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#886611"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O53" connection="9"/>
+        <dia:connection handle="1" to="O3" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O55">
+      <dia:attribute name="obj_pos">
+        <dia:point val="86.975,61.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="86.9079,35.85;96.05,62.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="86.975,61.7"/>
+        <dia:point val="94,61.7"/>
+        <dia:point val="94,35.9"/>
+        <dia:point val="96,35.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#886611"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O53" connection="11"/>
+        <dia:connection handle="1" to="O3" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O56">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,66.1"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,29.85;33.05,66.5809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,66.1"/>
+        <dia:point val="28,66.1"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O49" connection="17"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O57">
+      <dia:attribute name="obj_pos">
+        <dia:point val="22.82,72.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="22.7529,29.85;33.05,72.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="22.82,72.3"/>
+        <dia:point val="28,72.3"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O50" connection="15"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O58">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,65.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,20.85;33.05,65.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,65.3"/>
+        <dia:point val="29,65.3"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O49" connection="15"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O59">
+      <dia:attribute name="obj_pos">
+        <dia:point val="22.82,71.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="22.7529,20.85;33.05,71.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="22.82,71.5"/>
+        <dia:point val="29,71.5"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O50" connection="13"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O60">
+      <dia:attribute name="obj_pos">
+        <dia:point val="22.82,70.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="8.95,59.95;30.05,71.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="22.82,70.7"/>
+        <dia:point val="30,70.7"/>
+        <dia:point val="30,60"/>
+        <dia:point val="9,60"/>
+        <dia:point val="9,62.9"/>
+        <dia:point val="10,62.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#cc0066"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O50" connection="11"/>
+        <dia:connection handle="1" to="O49" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O61">
+      <dia:attribute name="obj_pos">
+        <dia:point val="25.515,56.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="25.4479,29.85;33.05,57.3809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="25.515,56.9"/>
+        <dia:point val="28,56.9"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O52" connection="19"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O62">
+      <dia:attribute name="obj_pos">
+        <dia:point val="25.515,56.1"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="25.4479,20.85;33.05,56.5809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="25.515,56.1"/>
+        <dia:point val="29,56.1"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O52" connection="17"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O63">
+      <dia:attribute name="obj_pos">
+        <dia:point val="25.515,55.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="25.4479,4.85;96.05,55.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="25.515,55.3"/>
+        <dia:point val="27,55.3"/>
+        <dia:point val="27,52"/>
+        <dia:point val="92,52"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O52" connection="15"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O64">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,60.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="31.95,56.95;51.05,61.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,60.7"/>
+        <dia:point val="51,60.7"/>
+        <dia:point val="51,57"/>
+        <dia:point val="32,57"/>
+        <dia:point val="32,59.9"/>
+        <dia:point val="33,59.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="11"/>
+        <dia:connection handle="1" to="O51" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O65">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,61.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="8.95,55.95;52.05,62.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,61.5"/>
+        <dia:point val="52,61.5"/>
+        <dia:point val="52,56"/>
+        <dia:point val="30,56"/>
+        <dia:point val="30,60"/>
+        <dia:point val="9,60"/>
+        <dia:point val="9,62.9"/>
+        <dia:point val="10,62.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#cc0066"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="13"/>
+        <dia:connection handle="1" to="O49" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O66">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,64.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,29.85;53.05,65.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,64.7"/>
+        <dia:point val="53,64.7"/>
+        <dia:point val="53,55"/>
+        <dia:point val="28,55"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="21"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O67">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,66.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,29.85;53.05,66.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,66.3"/>
+        <dia:point val="53,66.3"/>
+        <dia:point val="53,55"/>
+        <dia:point val="28,55"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="25"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O68">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,71.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,29.85;53.05,72.3809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,71.9"/>
+        <dia:point val="53,71.9"/>
+        <dia:point val="53,55"/>
+        <dia:point val="28,55"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="39"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O69">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,73.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,29.85;53.05,73.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,73.5"/>
+        <dia:point val="53,73.5"/>
+        <dia:point val="53,55"/>
+        <dia:point val="28,55"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="43"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O70">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,69.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="49.2179,38.85;73.05,69.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,69.5"/>
+        <dia:point val="71,69.5"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="33"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O71">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,70.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="49.2179,13.85;96.0707,70.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,70.3"/>
+        <dia:point val="70,70.3"/>
+        <dia:point val="70,20"/>
+        <dia:point val="91,20"/>
+        <dia:point val="91,13.9"/>
+        <dia:point val="96,13.9"/>
+        <dia:point val="96,13.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#ff8888"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="35"/>
+        <dia:connection handle="1" to="O1" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O72">
+      <dia:attribute name="obj_pos">
+        <dia:point val="49.285,71.1"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="49.2179,41.85;56.05,71.5809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="49.285,71.1"/>
+        <dia:point val="54,71.1"/>
+        <dia:point val="54,54"/>
+        <dia:point val="54,54"/>
+        <dia:point val="54,41.9"/>
+        <dia:point val="56,41.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#0000aa"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O51" connection="37"/>
+        <dia:connection handle="1" to="O11" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - Text" version="1" id="O73">
+      <dia:attribute name="obj_pos">
+        <dia:point val="91,79"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="77.765,78.255;104.235,79.19"/>
+      </dia:attribute>
+      <dia:attribute name="text">
+        <dia:composite type="text">
+          <dia:attribute name="string">
+            <dia:string>#Copyright © 2004-2010 Health Administration Corporation#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="font">
+            <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+          </dia:attribute>
+          <dia:attribute name="height">
+            <dia:real val="1"/>
+          </dia:attribute>
+          <dia:attribute name="pos">
+            <dia:point val="91,79"/>
+          </dia:attribute>
+          <dia:attribute name="color">
+            <dia:color val="#000000"/>
+          </dia:attribute>
+          <dia:attribute name="alignment">
+            <dia:enum val="1"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="valign">
+        <dia:enum val="3"/>
+      </dia:attribute>
+    </dia:object>
+    <dia:object type="Standard - Text" version="1" id="O74">
+      <dia:attribute name="obj_pos">
+        <dia:point val="91,76"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="82.8287,75.1075;99.1713,77.4275"/>
+      </dia:attribute>
+      <dia:attribute name="text">
+        <dia:composite type="text">
+          <dia:attribute name="string">
+            <dia:string>#NetEpi Collection E-R Diagram
+November 2010#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="font">
+            <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+          </dia:attribute>
+          <dia:attribute name="height">
+            <dia:real val="1.2"/>
+          </dia:attribute>
+          <dia:attribute name="pos">
+            <dia:point val="91,76"/>
+          </dia:attribute>
+          <dia:attribute name="color">
+            <dia:color val="#000000"/>
+          </dia:attribute>
+          <dia:attribute name="alignment">
+            <dia:enum val="1"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="valign">
+        <dia:enum val="3"/>
+      </dia:attribute>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O75">
+      <dia:attribute name="obj_pos">
+        <dia:point val="96,66"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="95.95,65.95;106.56,70.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="96,66"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#address_states#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#address_states_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#code#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O76">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,21"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,20.95;88.565,25.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,21"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#import_defs#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#import_defs_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#xmldef#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O77">
+      <dia:attribute name="obj_pos">
+        <dia:point val="73,67"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="72.95,66.95;78.94,70.25"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="73,67"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="5.8899999999999997"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="3.2000000000000002"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#nicknames#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#nick#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#alt#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O78">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,24.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,4.85;96.05,24.9809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,24.5"/>
+        <dia:point val="92,24.5"/>
+        <dia:point val="92,24"/>
+        <dia:point val="92,24"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O76" connection="13"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O79">
+      <dia:attribute name="obj_pos">
+        <dia:point val="51,24"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="50.95,23.95;67.72,28.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="51,24"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="16.670000000000002"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#case_contacts#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES case#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_type_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES contact_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_date#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#TIMESTAMP#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O80">
+      <dia:attribute name="obj_pos">
+        <dia:point val="51,31"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="50.95,30.95;60.79,34.25"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="51,31"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="9.7400000000000002"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="3.2000000000000002"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#contact_types#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_type_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#contact_type#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O81">
+      <dia:attribute name="obj_pos">
+        <dia:point val="67.67,25.9"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="67.6029,25.4191;73.05,38.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="67.67,25.9"/>
+        <dia:point val="71,25.9"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O79" connection="9"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O82">
+      <dia:attribute name="obj_pos">
+        <dia:point val="67.67,26.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="67.6029,26.2191;73.05,38.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="67.67,26.7"/>
+        <dia:point val="71,26.7"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O79" connection="11"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O83">
+      <dia:attribute name="obj_pos">
+        <dia:point val="67.67,27.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="49.95,27.0191;69.05,32.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="67.67,27.5"/>
+        <dia:point val="69,27.5"/>
+        <dia:point val="69,30"/>
+        <dia:point val="50,30"/>
+        <dia:point val="50,32.9"/>
+        <dia:point val="51,32.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#3388aa"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O79" connection="13"/>
+        <dia:connection handle="1" to="O80" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O84">
+      <dia:attribute name="obj_pos">
+        <dia:point val="96,28"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="95.95,27.95;111.565,32.85"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="96,28"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="15.515000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4.8000000000000007"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#syndrome_case_assignments#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndca_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#syndrome_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES syndrome_types#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#label#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O85">
+      <dia:attribute name="obj_pos">
+        <dia:point val="111.515,30.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="91.95,4.85;113.05,31.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="111.515,30.7"/>
+        <dia:point val="113,30.7"/>
+        <dia:point val="113,26"/>
+        <dia:point val="92,26"/>
+        <dia:point val="92,4.9"/>
+        <dia:point val="96,4.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa22ff"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O84" connection="11"/>
+        <dia:connection handle="1" to="O0" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O86">
+      <dia:attribute name="obj_pos">
+        <dia:point val="88.515,50.1"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="88.4479,31.45;96.05,50.5809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="88.515,50.1"/>
+        <dia:point val="93,50.1"/>
+        <dia:point val="93,31.5"/>
+        <dia:point val="96,31.5"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#0000aa"/>
+      </dia:attribute>
+      <dia:attribute name="line_style">
+        <dia:enum val="4"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O9" connection="37"/>
+        <dia:connection handle="1" to="O84" connection="12"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O87">
+      <dia:attribute name="obj_pos">
+        <dia:point val="107.665,48.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="94.95,48.2191;109.05,68.75"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="107.665,48.7"/>
+        <dia:point val="109,48.7"/>
+        <dia:point val="109,65"/>
+        <dia:point val="95,65"/>
+        <dia:point val="95,68.7"/>
+        <dia:point val="96,68.7"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#0000aa"/>
+      </dia:attribute>
+      <dia:attribute name="line_style">
+        <dia:enum val="4"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O3" connection="41"/>
+        <dia:connection handle="1" to="O75" connection="10"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O88">
+      <dia:attribute name="obj_pos">
+        <dia:point val="107.665,52.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="94.95,52.2191;109.05,68.75"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="107.665,52.7"/>
+        <dia:point val="109,52.7"/>
+        <dia:point val="109,65"/>
+        <dia:point val="95,65"/>
+        <dia:point val="95,68.7"/>
+        <dia:point val="96,68.7"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#0000aa"/>
+      </dia:attribute>
+      <dia:attribute name="line_style">
+        <dia:enum val="4"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O3" connection="51"/>
+        <dia:connection handle="1" to="O75" connection="10"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O89">
+      <dia:attribute name="obj_pos">
+        <dia:point val="107.665,56.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="94.95,56.2191;109.05,68.75"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="107.665,56.7"/>
+        <dia:point val="109,56.7"/>
+        <dia:point val="109,65"/>
+        <dia:point val="95,65"/>
+        <dia:point val="95,68.7"/>
+        <dia:point val="96,68.7"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#0000aa"/>
+      </dia:attribute>
+      <dia:attribute name="line_style">
+        <dia:enum val="4"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O3" connection="61"/>
+        <dia:connection handle="1" to="O75" connection="10"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O90">
+      <dia:attribute name="obj_pos">
+        <dia:point val="47.745,46.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="27.95,26.95;49.05,47.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="47.745,46.7"/>
+        <dia:point val="49,46.7"/>
+        <dia:point val="49,27"/>
+        <dia:point val="28,27"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O7" connection="51"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O91">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,24.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,24.0191;73.05,51.05"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,24.5"/>
+        <dia:point val="32,24.5"/>
+        <dia:point val="32,51"/>
+        <dia:point val="71,51"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O10" connection="13"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O92">
+      <dia:attribute name="obj_pos">
+        <dia:point val="56,59"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="55.95,58.95;62.325,63.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="56,59"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="6.2750000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#tags#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#tag_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#tag#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#notes#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O93">
+      <dia:attribute name="obj_pos">
+        <dia:point val="56,53"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="55.95,52.95;66.56,57.05"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="56,53"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="4"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#case_tags#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_tag_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#case_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES cases#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#tag_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES tags#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="UML - Class" version="0" id="O94">
+      <dia:attribute name="obj_pos">
+        <dia:point val="10,74"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="9.95,73.95;20.56,80.45"/>
+      </dia:attribute>
+      <dia:attribute name="elem_corner">
+        <dia:point val="10,74"/>
+      </dia:attribute>
+      <dia:attribute name="elem_width">
+        <dia:real val="10.51"/>
+      </dia:attribute>
+      <dia:attribute name="elem_height">
+        <dia:real val="6.4000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="name">
+        <dia:string>#casesets#</dia:string>
+      </dia:attribute>
+      <dia:attribute name="stereotype">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="comment">
+        <dia:string>##</dia:string>
+      </dia:attribute>
+      <dia:attribute name="abstract">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_attributes">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="suppress_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_attributes">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="visible_operations">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="visible_comments">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_operations">
+        <dia:boolean val="true"/>
+      </dia:attribute>
+      <dia:attribute name="wrap_after_char">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_line_length">
+        <dia:int val="40"/>
+      </dia:attribute>
+      <dia:attribute name="comment_tagging">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_width">
+        <dia:real val="0.10000000000000001"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="fill_color">
+        <dia:color val="#ffffff"/>
+      </dia:attribute>
+      <dia:attribute name="text_color">
+        <dia:color val="#000000"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font">
+        <dia:font family="monospace" style="0" name="Courier"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font">
+        <dia:font family="monospace" style="88" name="Courier-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font">
+        <dia:font family="monospace" style="8" name="Courier-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font">
+        <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font">
+        <dia:font family="sans" style="88" name="Helvetica-BoldOblique"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font">
+        <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+      </dia:attribute>
+      <dia:attribute name="normal_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="polymorphic_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_font_height">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="abstract_classname_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="comment_font_height">
+        <dia:real val="1"/>
+      </dia:attribute>
+      <dia:attribute name="attributes">
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#caseset_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#SERIAL#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#name#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#VARCHAR#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#dynamic#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BOOLEAN#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#unit_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES units#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#user_id#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#REFERENCES users#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+        <dia:composite type="umlattribute">
+          <dia:attribute name="name">
+            <dia:string>#pickle#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="type">
+            <dia:string>#BYTEA#</dia:string>
+          </dia:attribute>
+          <dia:attribute name="value">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="comment">
+            <dia:string>##</dia:string>
+          </dia:attribute>
+          <dia:attribute name="visibility">
+            <dia:enum val="0"/>
+          </dia:attribute>
+          <dia:attribute name="abstract">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+          <dia:attribute name="class_scope">
+            <dia:boolean val="false"/>
+          </dia:attribute>
+        </dia:composite>
+      </dia:attribute>
+      <dia:attribute name="operations"/>
+      <dia:attribute name="template">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="templates"/>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O95">
+      <dia:attribute name="obj_pos">
+        <dia:point val="66.51,55.7"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="66.4429,38.85;73.05,56.1809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="66.51,55.7"/>
+        <dia:point val="71,55.7"/>
+        <dia:point val="71,38.9"/>
+        <dia:point val="73,38.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#dd8800"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O93" connection="11"/>
+        <dia:connection handle="1" to="O9" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O96">
+      <dia:attribute name="obj_pos">
+        <dia:point val="66.51,56.5"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="54.95,56.0191;68.05,60.95"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="66.51,56.5"/>
+        <dia:point val="68,56.5"/>
+        <dia:point val="68,58"/>
+        <dia:point val="55,58"/>
+        <dia:point val="55,60.9"/>
+        <dia:point val="56,60.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O93" connection="13"/>
+        <dia:connection handle="1" to="O92" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O97">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,78.3"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,20.85;33.05,78.7809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,78.3"/>
+        <dia:point val="29,78.3"/>
+        <dia:point val="29,20.9"/>
+        <dia:point val="33,20.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#aa2222"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O94" connection="15"/>
+        <dia:connection handle="1" to="O6" connection="8"/>
+      </dia:connections>
+    </dia:object>
+    <dia:object type="Standard - ZigZagLine" version="1" id="O98">
+      <dia:attribute name="obj_pos">
+        <dia:point val="20.51,79.1"/>
+      </dia:attribute>
+      <dia:attribute name="obj_bb">
+        <dia:rectangle val="20.4429,29.85;33.05,79.5809"/>
+      </dia:attribute>
+      <dia:attribute name="orth_points">
+        <dia:point val="20.51,79.1"/>
+        <dia:point val="28,79.1"/>
+        <dia:point val="28,29.9"/>
+        <dia:point val="33,29.9"/>
+      </dia:attribute>
+      <dia:attribute name="orth_orient">
+        <dia:enum val="0"/>
+        <dia:enum val="1"/>
+        <dia:enum val="0"/>
+      </dia:attribute>
+      <dia:attribute name="autorouting">
+        <dia:boolean val="false"/>
+      </dia:attribute>
+      <dia:attribute name="line_color">
+        <dia:color val="#55bb55"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow">
+        <dia:enum val="20"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_length">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:attribute name="start_arrow_width">
+        <dia:real val="0.80000000000000004"/>
+      </dia:attribute>
+      <dia:connections>
+        <dia:connection handle="0" to="O94" connection="17"/>
+        <dia:connection handle="1" to="O7" connection="8"/>
+      </dia:connections>
+    </dia:object>
+  </dia:layer>
+</dia:diagram>
diff --git a/doc/Collection-ER.pdf b/doc/Collection-ER.pdf
new file mode 100644
index 0000000..974a759
Binary files /dev/null and b/doc/Collection-ER.pdf differ
diff --git a/doc/Collection-ER.png b/doc/Collection-ER.png
new file mode 100644
index 0000000..12006ac
Binary files /dev/null and b/doc/Collection-ER.png differ
diff --git a/doc/CollectionApp.dot b/doc/CollectionApp.dot
new file mode 100644
index 0000000..10d0ed1
--- /dev/null
+++ b/doc/CollectionApp.dot
@@ -0,0 +1,217 @@
+digraph appname {
+pack=true;
+center=true;
+overlap=scale;
+splines=true;
+nodesep=.2;
+epsilon=.1;
+mclimit=16;
+rankdir=LR;
+size="11.69x8.27";
+node [shape=box,fontname="Verdana"]
+admin;
+admin_address_states;
+admin_bulletin;
+admin_bulletins;
+admin_form_deploy;
+admin_form_edit;
+admin_form_edit_question;
+admin_forms;
+admin_group;
+admin_groups;
+admin_queue;
+admin_queues;
+admin_synd_clear;
+admin_synd_drop;
+admin_synd_fields;
+admin_syndrome;
+admin_syndromes;
+admin_synd_status;
+admin_unit;
+admin_units;
+admin_user;
+admin_users;
+admin_wikiedit;
+assoccontact;
+bulletin_detail;
+case;
+caseaccess;
+casecontact;
+casecontacts;
+caseform;
+caseprint;
+casetask;
+casetasks;
+contactvis;
+dataimp;
+dataimp_editfield;
+dupepersons;
+dupepersons_config;
+dupepersons_config_edit;
+export;
+followup;
+login;
+logview;
+main;
+manualdupe;
+mergecase;
+mergecase_detail;
+mergeform;
+mergeform_detail;
+mergeperson;
+mergeperson_detail;
+newcase;
+newcontact;
+notetask;
+prefsedit;
+printforms;
+reports;
+report_table;
+result;
+search;
+selcontactvis;
+selmergecase;
+selmergeforms;
+selprintforms;
+syn_detail;
+taskaction;
+taskedit;
+tasks;
+tools;
+unitview;
+useradmin;
+useredit;
+user_queue;
+user_queues;
+
+node [shape=ellipse]
+edge [style=bold,weight=1]
+admin -> admin_address_states;
+admin -> admin_bulletin;
+admin -> admin_bulletins;
+admin -> admin_form_edit;
+admin -> admin_forms;
+admin -> admin_group;
+admin -> admin_groups;
+admin -> admin_queue;
+admin -> admin_queues;
+admin -> admin_synd_fields;
+admin -> admin_syndrome;
+admin -> admin_syndromes;
+admin -> admin_unit;
+admin -> admin_units;
+admin -> admin_user;
+admin -> admin_users;
+admin_bulletin -> admin_wikiedit;
+admin_bulletins -> admin_bulletin;
+admin_form_edit -> admin_form_deploy;
+admin_form_edit -> admin_form_edit_question;
+admin_forms -> admin_form_edit;
+admin_groups -> admin_group;
+admin_queues -> admin_queue;
+admin_syndrome -> admin_address_states;
+admin_syndrome -> admin_synd_clear;
+admin_syndrome -> admin_synd_drop;
+admin_syndrome -> admin_synd_fields;
+admin_syndrome -> admin_synd_status;
+admin_syndrome -> admin_wikiedit;
+admin_syndromes -> admin_syndrome;
+admin_unit -> admin_users;
+admin_units -> admin_unit;
+admin_user -> logview;
+admin_users -> admin_user;
+admin_users -> logview;
+assoccontact -> result;
+bulletin_detail -> bulletin_detail;
+caseaccess -> unitview;
+case -> assoccontact;
+case -> caseaccess;
+case -> casecontacts;
+case -> caseform;
+case -> caseprint;
+case -> casetask;
+case -> casetasks;
+casecontact -> case;
+casecontact -> caseprint;
+casecontact -> casetask;
+casecontact -> casetasks;
+casecontact -> followup;
+casecontact -> logview;
+casecontacts -> case;
+casecontacts -> casecontact;
+casecontact -> selmergecase;
+casecontact -> selmergeforms;
+casecontacts -> newcontact;
+caseform -> caseprint;
+caseform -> casetask;
+case -> logview;
+case -> selmergecase;
+case -> selmergeforms;
+casetasks -> taskaction;
+casetask -> taskedit;
+notetask -> taskedit;
+dataimp -> dataimp_editfield;
+dupepersons -> mergeperson;
+followup -> caseprint;
+followup -> casetask;
+login -> main;
+login -> useredit;
+main -> admin;
+main -> bulletin_detail;
+main -> dataimp;
+main -> demog_imp;
+main -> export;
+main -> newcase;
+main -> reports;
+main -> report_table;
+main -> search;
+main -> selcontactvis;
+main -> selprintforms;
+main -> syn_detail;
+main -> tasks;
+main -> tools;
+manualdupe -> mergeperson;
+mergecase_detail -> selmergeforms;
+mergecase -> mergecase_detail;
+mergeform -> mergeform_detail;
+mergeperson_detail -> selmergecase;
+mergeperson -> mergeperson_detail;
+newcase -> case;
+newcase -> result;
+newcontact -> casecontact;
+newcontact -> result;
+reports -> report_table;
+reports -> selcontactvis;
+result -> case;
+result -> casecontact;
+search -> result;
+selcontactvis -> contactvis;
+selmergecase -> mergecase;
+selmergeforms -> mergeform;
+selprintforms -> printforms;
+taskaction -> case;
+taskaction -> casecontact;
+taskaction -> caseform;
+taskaction -> casetask;
+taskaction -> followup;
+tasks -> taskaction;
+tools -> dupepersons;
+tools -> logview;
+tools -> manualdupe;
+tools -> prefsedit;
+tools -> useradmin;
+tools -> useredit;
+tools -> user_queues;
+useradmin -> logview;
+useradmin -> useredit;
+user_queues -> user_queue;
+tools -> dupepersons_config;
+dupepersons_config -> dupepersons_config_edit;
+casetasks -> notetask;
+taskaction -> notetask;
+tasks -> notetask;
+taskedit -> unitview;
+
+edge [style=solid,weight=4]
+edge [style=dotted,weight=2]
+}
diff --git a/doc/CollectionApp.svg b/doc/CollectionApp.svg
new file mode 100644
index 0000000..0da77c3
--- /dev/null
+++ b/doc/CollectionApp.svg
@@ -0,0 +1,2994 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Graphviz version 2.20.2 (Sat Aug 16 05:41:32 UTC 2008)
+     For user: (andrewm) Andrew McNamara -->
+<!-- Title: appname Pages: 1 -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1052.3622"
+   height="744.09448"
+   viewBox="0 0 2260 1533"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="x.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   version="1.0">
+  <metadata
+     id="metadata1412">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs1410">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 958.125 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="2825 : 958.125 : 1"
+       inkscape:persp3d-origin="1412.5 : 638.75 : 1"
+       id="perspective1414" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:window-height="1133"
+     inkscape:window-width="1311"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     guidetolerance="10.0"
+     gridtolerance="10.0"
+     objecttolerance="10.0"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     showgrid="false"
+     inkscape:zoom="1.0873175"
+     inkscape:cx="560.16941"
+     inkscape:cy="332.73268"
+     inkscape:window-x="273"
+     inkscape:window-y="31"
+     inkscape:current-layer="svg2"
+     units="cm" />
+  <g
+     id="graph0"
+     class="graph"
+     transform="matrix(0.9283482,0,0,0.9308061,76.800407,1468.1994)">
+    <title
+       id="title5">appname</title>
+    <polygon
+       style="fill:#ffffff;stroke:#ffffff"
+       points="-4,4 -4,-1529 2256,-1529 2256,4 -4,4 "
+       id="polygon7" />
+<!-- admin -->    <g
+       id="node1"
+       class="node">
+      <title
+         id="title10">admin</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="457,-846 397,-846 397,-810 457,-810 457,-846 "
+         id="polygon12" />
+      <text
+         x="427"
+         y="-823.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text14">admin</text>
+    </g>
+<!-- admin_address_states -->    <g
+       id="node2"
+       class="node">
+      <title
+         id="title17">admin_address_states</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1109,-761 933,-761 933,-725 1109,-725 1109,-761 "
+         id="polygon19" />
+      <text
+         x="1021"
+         y="-738.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text21">admin_address_states</text>
+    </g>
+<!-- admin->admin_address_states -->    <g
+       id="edge2"
+       class="edge">
+      <title
+         id="title24">admin->admin_address_states</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 449,-810 C 470,-794 502,-773 534,-763 C 665,-723 823,-726 923,-733"
+         id="path26" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="922.701,-736.488 933,-734 923.398,-729.522 922.701,-736.488 "
+         id="polygon28" />
+    </g>
+<!-- admin_bulletin -->    <g
+       id="node3"
+       class="node">
+      <title
+         id="title31">admin_bulletin</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="860,-811 738,-811 738,-775 860,-775 860,-811 "
+         id="polygon33" />
+      <text
+         x="799"
+         y="-788.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text35">admin_bulletin</text>
+    </g>
+<!-- admin->admin_bulletin -->    <g
+       id="edge4"
+       class="edge">
+      <title
+         id="title38">admin->admin_bulletin</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-827 C 505,-826 601,-822 682,-813 C 697,-811 713,-809 727,-806"
+         id="path40" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="727.881,-809.393 737,-804 726.508,-802.529 727.881,-809.393 "
+         id="polygon42" />
+    </g>
+<!-- admin_bulletins -->    <g
+       id="node4"
+       class="node">
+      <title
+         id="title45">admin_bulletins</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="673,-806 543,-806 543,-770 673,-770 673,-806 "
+         id="polygon47" />
+      <text
+         x="608"
+         y="-783.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text49">admin_bulletins</text>
+    </g>
+<!-- admin->admin_bulletins -->    <g
+       id="edge6"
+       class="edge">
+      <title
+         id="title52">admin->admin_bulletins</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-821 C 477,-817 506,-811 532,-805"
+         id="path54" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="532.881,-808.393 542,-803 531.508,-801.529 532.881,-808.393 "
+         id="polygon56" />
+    </g>
+<!-- admin_form_edit -->    <g
+       id="node6"
+       class="node">
+      <title
+         id="title59">admin_form_edit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="868,-639 730,-639 730,-603 868,-603 868,-639 "
+         id="polygon61" />
+      <text
+         x="799"
+         y="-616.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text63">admin_form_edit</text>
+    </g>
+<!-- admin->admin_form_edit -->    <g
+       id="edge8"
+       class="edge">
+      <title
+         id="title66">admin->admin_form_edit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 429,-810 C 436,-765 460,-650 534,-603 C 598,-562 688,-581 745,-600"
+         id="path68" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="744.416,-603.479 755,-603 746.427,-596.774 744.416,-603.479 "
+         id="polygon70" />
+    </g>
+<!-- admin_forms -->    <g
+       id="node8"
+       class="node">
+      <title
+         id="title73">admin_forms</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="663,-646 553,-646 553,-610 663,-610 663,-646 "
+         id="polygon75" />
+      <text
+         x="608"
+         y="-623.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text77">admin_forms</text>
+    </g>
+<!-- admin->admin_forms -->    <g
+       id="edge10"
+       class="edge">
+      <title
+         id="title80">admin->admin_forms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 433,-810 C 446,-774 480,-696 534,-653 C 537,-651 540,-649 543,-647"
+         id="path82" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="544.958,-649.916 552,-642 541.559,-643.797 544.958,-649.916 "
+         id="polygon84" />
+    </g>
+<!-- admin_group -->    <g
+       id="node9"
+       class="node">
+      <title
+         id="title87">admin_group</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="854,-711 744,-711 744,-675 854,-675 854,-711 "
+         id="polygon89" />
+      <text
+         x="799"
+         y="-688.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text91">admin_group</text>
+    </g>
+<!-- admin->admin_group -->    <g
+       id="edge12"
+       class="edge">
+      <title
+         id="title94">admin->admin_group</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 443,-810 C 462,-789 497,-754 534,-737 C 569,-721 666,-707 733,-700"
+         id="path96" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="733.398,-703.478 743,-699 732.701,-696.512 733.398,-703.478 "
+         id="polygon98" />
+    </g>
+<!-- admin_groups -->    <g
+       id="node10"
+       class="node">
+      <title
+         id="title101">admin_groups</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="667,-696 549,-696 549,-660 667,-660 667,-696 "
+         id="polygon103" />
+      <text
+         x="608"
+         y="-673.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text105">admin_groups</text>
+    </g>
+<!-- admin->admin_groups -->    <g
+       id="edge14"
+       class="edge">
+      <title
+         id="title108">admin->admin_groups</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 439,-810 C 457,-784 493,-736 534,-707 C 537,-705 540,-703 543,-701"
+         id="path110" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="544.958,-703.916 552,-696 541.559,-697.797 544.958,-703.916 "
+         id="polygon112" />
+    </g>
+<!-- admin_queue -->    <g
+       id="node11"
+       class="node">
+      <title
+         id="title115">admin_queue</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="855,-1011 743,-1011 743,-975 855,-975 855,-1011 "
+         id="polygon117" />
+      <text
+         x="799"
+         y="-988.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text119">admin_queue</text>
+    </g>
+<!-- admin->admin_queue -->    <g
+       id="edge16"
+       class="edge">
+      <title
+         id="title122">admin->admin_queue</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-834 C 471,-838 487,-845 498,-857 C 529,-888 500,-921 534,-949 C 549,-960 659,-976 732,-985"
+         id="path124" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="731.701,-988.488 742,-986 732.398,-981.522 731.701,-988.488 "
+         id="polygon126" />
+    </g>
+<!-- admin_queues -->    <g
+       id="node12"
+       class="node">
+      <title
+         id="title129">admin_queues</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="668,-1026 548,-1026 548,-990 668,-990 668,-1026 "
+         id="polygon131" />
+      <text
+         x="608"
+         y="-1003.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text133">admin_queues</text>
+    </g>
+<!-- admin->admin_queues -->    <g
+       id="edge18"
+       class="edge">
+      <title
+         id="title136">admin->admin_queues</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-834 C 471,-838 488,-845 498,-857 C 536,-898 496,-937 534,-979 C 536,-981 538,-983 540,-984"
+         id="path138" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="537.9,-986.8 548,-990 542.1,-981.2 537.9,-986.8 "
+         id="polygon140" />
+    </g>
+<!-- admin_synd_fields -->    <g
+       id="node15"
+       class="node">
+      <title
+         id="title143">admin_synd_fields</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1096,-1011 946,-1011 946,-975 1096,-975 1096,-1011 "
+         id="polygon145" />
+      <text
+         x="1021"
+         y="-988.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text147">admin_synd_fields</text>
+    </g>
+<!-- admin->admin_synd_fields -->    <g
+       id="edge20"
+       class="edge">
+      <title
+         id="title150">admin->admin_synd_fields</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-836 C 471,-840 486,-847 498,-857 C 521,-875 510,-895 534,-911 C 600,-953 804,-954 880,-968 C 898,-971 917,-974 935,-977"
+         id="path152" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="934.508,-980.471 945,-979 935.881,-973.607 934.508,-980.471 "
+         id="polygon154" />
+    </g>
+<!-- admin_syndrome -->    <g
+       id="node16"
+       class="node">
+      <title
+         id="title157">admin_syndrome</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="869,-901 729,-901 729,-865 869,-865 869,-901 "
+         id="polygon159" />
+      <text
+         x="799"
+         y="-878.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text161">admin_syndrome</text>
+    </g>
+<!-- admin->admin_syndrome -->    <g
+       id="edge22"
+       class="edge">
+      <title
+         id="title164">admin->admin_syndrome</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-837 C 470,-842 486,-849 498,-857 C 517,-869 514,-884 534,-893 C 592,-917 665,-913 720,-903"
+         id="path166" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="720.881,-906.393 730,-901 719.508,-899.529 720.881,-906.393 "
+         id="polygon168" />
+    </g>
+<!-- admin_syndromes -->    <g
+       id="node17"
+       class="node">
+      <title
+         id="title171">admin_syndromes</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="681,-886 535,-886 535,-850 681,-850 681,-886 "
+         id="polygon173" />
+      <text
+         x="608"
+         y="-863.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text175">admin_syndromes</text>
+    </g>
+<!-- admin->admin_syndromes -->    <g
+       id="edge24"
+       class="edge">
+      <title
+         id="title178">admin->admin_syndromes</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-835 C 475,-839 500,-844 524,-850"
+         id="path180" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="523.508,-853.471 534,-852 524.881,-846.607 523.508,-853.471 "
+         id="polygon182" />
+    </g>
+<!-- admin_unit -->    <g
+       id="node19"
+       class="node">
+      <title
+         id="title185">admin_unit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="847,-1106 751,-1106 751,-1070 847,-1070 847,-1106 "
+         id="polygon187" />
+      <text
+         x="799"
+         y="-1083.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text189">admin_unit</text>
+    </g>
+<!-- admin->admin_unit -->    <g
+       id="edge26"
+       class="edge">
+      <title
+         id="title192">admin->admin_unit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-833 C 472,-837 488,-844 498,-857 C 569,-947 448,-1037 534,-1113 C 564,-1139 672,-1119 740,-1103"
+         id="path194" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="740.881,-1106.39 750,-1101 739.508,-1099.53 740.881,-1106.39 "
+         id="polygon196" />
+    </g>
+<!-- admin_units -->    <g
+       id="node20"
+       class="node">
+      <title
+         id="title199">admin_units</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="660,-1106 556,-1106 556,-1070 660,-1070 660,-1106 "
+         id="polygon201" />
+      <text
+         x="608"
+         y="-1083.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text203">admin_units</text>
+    </g>
+<!-- admin->admin_units -->    <g
+       id="edge28"
+       class="edge">
+      <title
+         id="title206">admin->admin_units</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-833 C 472,-837 488,-844 498,-857 C 556,-927 477,-988 534,-1059 C 538,-1063 542,-1067 546,-1070"
+         id="path208" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="544.559,-1073.2 555,-1075 547.958,-1067.08 544.559,-1073.2 "
+         id="polygon210" />
+    </g>
+<!-- admin_user -->    <g
+       id="node21"
+       class="node">
+      <title
+         id="title213">admin_user</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1292,-1139 1192,-1139 1192,-1103 1292,-1103 1292,-1139 "
+         id="polygon215" />
+      <text
+         x="1242"
+         y="-1116.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text217">admin_user</text>
+    </g>
+<!-- admin->admin_user -->    <g
+       id="edge30"
+       class="edge">
+      <title
+         id="title220">admin->admin_user</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 451,-846 C 467,-860 487,-880 498,-902 C 544,-993 457,-1062 534,-1129 C 558,-1150 1015,-1131 1182,-1123"
+         id="path222" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1182,-1126.5 1192,-1123 1182,-1119.5 1182,-1126.5 "
+         id="polygon224" />
+    </g>
+<!-- admin_users -->    <g
+       id="node22"
+       class="node">
+      <title
+         id="title227">admin_users</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1075,-1104 967,-1104 967,-1068 1075,-1068 1075,-1104 "
+         id="polygon229" />
+      <text
+         x="1021"
+         y="-1081.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text231">admin_users</text>
+    </g>
+<!-- admin->admin_users -->    <g
+       id="edge32"
+       class="edge">
+      <title
+         id="title234">admin->admin_users</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 457,-833 C 472,-837 488,-844 498,-857 C 549,-918 475,-979 534,-1033 C 563,-1058 842,-1058 880,-1063 C 905,-1066 933,-1070 957,-1074"
+         id="path236" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="956.508,-1077.47 967,-1076 957.881,-1070.61 956.508,-1077.47 "
+         id="polygon238" />
+    </g>
+<!-- admin_wikiedit -->    <g
+       id="node23"
+       class="node">
+      <title
+         id="title241">admin_wikiedit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1084,-811 958,-811 958,-775 1084,-775 1084,-811 "
+         id="polygon243" />
+      <text
+         x="1021"
+         y="-788.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text245">admin_wikiedit</text>
+    </g>
+<!-- admin_bulletin->admin_wikiedit -->    <g
+       id="edge34"
+       class="edge">
+      <title
+         id="title248">admin_bulletin->admin_wikiedit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 861,-793 C 888,-793 919,-793 948,-793"
+         id="path250" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="948,-796.5 958,-793 948,-789.5 948,-796.5 "
+         id="polygon252" />
+    </g>
+<!-- admin_bulletins->admin_bulletin -->    <g
+       id="edge36"
+       class="edge">
+      <title
+         id="title255">admin_bulletins->admin_bulletin</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 674,-790 C 691,-790 709,-791 727,-791"
+         id="path257" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="727,-794.5 737,-791 727,-787.5 727,-794.5 "
+         id="polygon259" />
+    </g>
+<!-- admin_form_deploy -->    <g
+       id="node5"
+       class="node">
+      <title
+         id="title262">admin_form_deploy</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1101,-681 941,-681 941,-645 1101,-645 1101,-681 "
+         id="polygon264" />
+      <text
+         x="1021"
+         y="-658.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text266">admin_form_deploy</text>
+    </g>
+<!-- admin_form_edit->admin_form_deploy -->    <g
+       id="edge38"
+       class="edge">
+      <title
+         id="title269">admin_form_edit->admin_form_deploy</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 869,-634 C 889,-637 910,-642 931,-646"
+         id="path271" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="930.508,-649.471 941,-648 931.881,-642.607 930.508,-649.471 "
+         id="polygon273" />
+    </g>
+<!-- admin_form_edit_question -->    <g
+       id="node7"
+       class="node">
+      <title
+         id="title276">admin_form_edit_question</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1126,-631 916,-631 916,-595 1126,-595 1126,-631 "
+         id="polygon278" />
+      <text
+         x="1021"
+         y="-608.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text280">admin_form_edit_question</text>
+    </g>
+<!-- admin_form_edit->admin_form_edit_question -->    <g
+       id="edge40"
+       class="edge">
+      <title
+         id="title283">admin_form_edit->admin_form_edit_question</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 869,-618 C 881,-618 893,-617 906,-617"
+         id="path285" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="906,-620.5 916,-617 906,-613.5 906,-620.5 "
+         id="polygon287" />
+    </g>
+<!-- admin_forms->admin_form_edit -->    <g
+       id="edge42"
+       class="edge">
+      <title
+         id="title290">admin_forms->admin_form_edit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 664,-626 C 681,-625 701,-624 719,-624"
+         id="path292" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="719,-627.5 729,-624 719,-620.5 719,-627.5 "
+         id="polygon294" />
+    </g>
+<!-- admin_groups->admin_group -->    <g
+       id="edge44"
+       class="edge">
+      <title
+         id="title297">admin_groups->admin_group</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 668,-683 C 689,-685 712,-686 733,-688"
+         id="path299" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="732.701,-691.488 743,-689 733.398,-684.522 732.701,-691.488 "
+         id="polygon301" />
+    </g>
+<!-- admin_queues->admin_queue -->    <g
+       id="edge46"
+       class="edge">
+      <title
+         id="title304">admin_queues->admin_queue</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 668,-1003 C 689,-1001 711,-1000 732,-998"
+         id="path306" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="732.398,-1001.48 742,-997 731.701,-994.512 732.398,-1001.48 "
+         id="polygon308" />
+    </g>
+<!-- admin_synd_clear -->    <g
+       id="node13"
+       class="node">
+      <title
+         id="title311">admin_synd_clear</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1094,-861 948,-861 948,-825 1094,-825 1094,-861 "
+         id="polygon313" />
+      <text
+         x="1021"
+         y="-838.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text315">admin_synd_clear</text>
+    </g>
+<!-- admin_synd_drop -->    <g
+       id="node14"
+       class="node">
+      <title
+         id="title318">admin_synd_drop</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1093,-961 949,-961 949,-925 1093,-925 1093,-961 "
+         id="polygon320" />
+      <text
+         x="1021"
+         y="-938.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text322">admin_synd_drop</text>
+    </g>
+<!-- admin_syndrome->admin_address_states -->    <g
+       id="edge48"
+       class="edge">
+      <title
+         id="title325">admin_syndrome->admin_address_states</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 825,-865 C 842,-853 864,-835 880,-818 C 899,-798 894,-783 916,-768 C 918,-766 921,-764 924,-763"
+         id="path327" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="925.283,-766.26 933,-759 922.44,-759.863 925.283,-766.26 "
+         id="polygon329" />
+    </g>
+<!-- admin_syndrome->admin_synd_clear -->    <g
+       id="edge50"
+       class="edge">
+      <title
+         id="title332">admin_syndrome->admin_synd_clear</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 869,-870 C 891,-867 915,-862 937,-858"
+         id="path334" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="937.881,-861.393 947,-856 936.508,-854.529 937.881,-861.393 "
+         id="polygon336" />
+    </g>
+<!-- admin_syndrome->admin_synd_drop -->    <g
+       id="edge52"
+       class="edge">
+      <title
+         id="title339">admin_syndrome->admin_synd_drop</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 859,-901 C 877,-907 897,-913 916,-918 C 923,-920 931,-922 938,-924"
+         id="path341" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="937.508,-927.471 948,-926 938.881,-920.607 937.508,-927.471 "
+         id="polygon343" />
+    </g>
+<!-- admin_syndrome->admin_synd_fields -->    <g
+       id="edge54"
+       class="edge">
+      <title
+         id="title346">admin_syndrome->admin_synd_fields</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 839,-901 C 853,-908 868,-917 880,-927 C 899,-942 896,-955 916,-968 C 922,-971 929,-974 936,-977"
+         id="path348" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="934.44,-980.137 945,-981 937.283,-973.74 934.44,-980.137 "
+         id="polygon350" />
+    </g>
+<!-- admin_synd_status -->    <g
+       id="node18"
+       class="node">
+      <title
+         id="title353">admin_synd_status</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1098,-911 944,-911 944,-875 1098,-875 1098,-911 "
+         id="polygon355" />
+      <text
+         x="1021"
+         y="-888.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text357">admin_synd_status</text>
+    </g>
+<!-- admin_syndrome->admin_synd_status -->    <g
+       id="edge56"
+       class="edge">
+      <title
+         id="title360">admin_syndrome->admin_synd_status</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 869,-886 C 890,-887 912,-888 933,-889"
+         id="path362" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="933,-892.5 943,-889 933,-885.5 933,-892.5 "
+         id="polygon364" />
+    </g>
+<!-- admin_syndrome->admin_wikiedit -->    <g
+       id="edge58"
+       class="edge">
+      <title
+         id="title367">admin_syndrome->admin_wikiedit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 826,-865 C 850,-850 884,-830 916,-818 C 926,-814 937,-810 948,-807"
+         id="path369" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="948.881,-810.393 958,-805 947.508,-803.529 948.881,-810.393 "
+         id="polygon371" />
+    </g>
+<!-- admin_syndromes->admin_syndrome -->    <g
+       id="edge60"
+       class="edge">
+      <title
+         id="title374">admin_syndromes->admin_syndrome</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 682,-874 C 694,-875 707,-876 719,-877"
+         id="path376" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="718.701,-880.488 729,-878 719.398,-873.522 718.701,-880.488 "
+         id="polygon378" />
+    </g>
+<!-- admin_unit->admin_users -->    <g
+       id="edge62"
+       class="edge">
+      <title
+         id="title381">admin_unit->admin_users</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 848,-1088 C 880,-1087 922,-1087 957,-1087"
+         id="path383" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="957,-1090.5 967,-1087 957,-1083.5 957,-1090.5 "
+         id="polygon385" />
+    </g>
+<!-- admin_units->admin_unit -->    <g
+       id="edge64"
+       class="edge">
+      <title
+         id="title388">admin_units->admin_unit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 661,-1088 C 686,-1088 715,-1088 740,-1088"
+         id="path390" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="740,-1091.5 750,-1088 740,-1084.5 740,-1091.5 "
+         id="polygon392" />
+    </g>
+<!-- logview -->    <g
+       id="node43"
+       class="node">
+      <title
+         id="title395">logview</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1451,-1119 1379,-1119 1379,-1083 1451,-1083 1451,-1119 "
+         id="polygon397" />
+      <text
+         x="1415"
+         y="-1096.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text399">logview</text>
+    </g>
+<!-- admin_user->logview -->    <g
+       id="edge66"
+       class="edge">
+      <title
+         id="title402">admin_user->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1292,-1115 C 1316,-1112 1345,-1109 1369,-1106"
+         id="path404" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1369.4,-1109.48 1379,-1105 1368.7,-1102.51 1369.4,-1109.48 "
+         id="polygon406" />
+    </g>
+<!-- admin_users->admin_user -->    <g
+       id="edge68"
+       class="edge">
+      <title
+         id="title409">admin_users->admin_user</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1075,-1095 C 1107,-1100 1148,-1107 1182,-1112"
+         id="path411" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1181.7,-1115.49 1192,-1113 1182.4,-1108.52 1181.7,-1115.49 "
+         id="polygon413" />
+    </g>
+<!-- admin_users->logview -->    <g
+       id="edge70"
+       class="edge">
+      <title
+         id="title416">admin_users->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1075,-1087 C 1135,-1089 1236,-1092 1322,-1096 C 1337,-1096 1354,-1097 1369,-1098"
+         id="path418" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1368.7,-1101.49 1379,-1099 1369.4,-1094.52 1368.7,-1101.49 "
+         id="polygon420" />
+    </g>
+<!-- assoccontact -->    <g
+       id="node24"
+       class="node">
+      <title
+         id="title423">assoccontact</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="662,-356 554,-356 554,-320 662,-320 662,-356 "
+         id="polygon425" />
+      <text
+         x="608"
+         y="-333.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text427">assoccontact</text>
+    </g>
+<!-- result -->    <g
+       id="node59"
+       class="node">
+      <title
+         id="title430">result</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1049,-356 993,-356 993,-320 1049,-320 1049,-356 "
+         id="polygon432" />
+      <text
+         x="1021"
+         y="-333.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text434">result</text>
+    </g>
+<!-- assoccontact->result -->    <g
+       id="edge72"
+       class="edge">
+      <title
+         id="title437">assoccontact->result</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 662,-338 C 747,-338 908,-338 982,-338"
+         id="path439" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="982,-341.5 992,-338 982,-334.5 982,-341.5 "
+         id="polygon441" />
+    </g>
+<!-- bulletin_detail -->    <g
+       id="node25"
+       class="node">
+      <title
+         id="title444">bulletin_detail</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="309,-926 191,-926 191,-890 309,-890 309,-926 "
+         id="polygon446" />
+      <text
+         x="250"
+         y="-903.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text448">bulletin_detail</text>
+    </g>
+<!-- bulletin_detail->bulletin_detail -->    <g
+       id="edge74"
+       class="edge">
+      <title
+         id="title451">bulletin_detail->bulletin_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 225,-926 C 224,-934 232,-940 250,-940 C 260,-940 267,-938 271,-935"
+         id="path453" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="274.137,-936.56 275,-926 267.74,-933.717 274.137,-936.56 "
+         id="polygon455" />
+    </g>
+<!-- case -->    <g
+       id="node26"
+       class="node">
+      <title
+         id="title458">case</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="454,-336 400,-336 400,-300 454,-300 454,-336 "
+         id="polygon460" />
+      <text
+         x="427"
+         y="-313.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text462">case</text>
+    </g>
+<!-- case->assoccontact -->    <g
+       id="edge78"
+       class="edge">
+      <title
+         id="title465">case->assoccontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-321 C 477,-324 513,-328 544,-331"
+         id="path467" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="543.701,-334.488 554,-332 544.398,-327.522 543.701,-334.488 "
+         id="polygon469" />
+    </g>
+<!-- caseaccess -->    <g
+       id="node27"
+       class="node">
+      <title
+         id="title472">caseaccess</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1602,-476 1508,-476 1508,-440 1602,-440 1602,-476 "
+         id="polygon474" />
+      <text
+         x="1555"
+         y="-453.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text476">caseaccess</text>
+    </g>
+<!-- case->caseaccess -->    <g
+       id="edge80"
+       class="edge">
+      <title
+         id="title479">case->caseaccess</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 451,-336 C 472,-352 504,-374 534,-389 C 732,-484 802,-468 1021,-468 C 1021,-468 1021,-468 1242,-468 C 1332,-468 1435,-464 1498,-460"
+         id="path481" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1498,-463.5 1508,-460 1498,-456.5 1498,-463.5 "
+         id="polygon483" />
+    </g>
+<!-- casecontacts -->    <g
+       id="node29"
+       class="node">
+      <title
+         id="title486">casecontacts</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="661,-246 555,-246 555,-210 661,-210 661,-246 "
+         id="polygon488" />
+      <text
+         x="608"
+         y="-223.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text490">casecontacts</text>
+    </g>
+<!-- case->casecontacts -->    <g
+       id="edge82"
+       class="edge">
+      <title
+         id="title493">case->casecontacts</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-305 C 468,-300 485,-294 498,-286 C 518,-273 516,-262 534,-250 C 537,-248 541,-246 545,-243"
+         id="path495" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="546.283,-246.26 554,-239 543.44,-239.863 546.283,-246.26 "
+         id="polygon497" />
+    </g>
+<!-- caseform -->    <g
+       id="node30"
+       class="node">
+      <title
+         id="title500">caseform</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1749,-211 1669,-211 1669,-175 1749,-175 1749,-211 "
+         id="polygon502" />
+      <text
+         x="1709"
+         y="-188.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text504">caseform</text>
+    </g>
+<!-- case->caseform -->    <g
+       id="edge84"
+       class="edge">
+      <title
+         id="title507">case->caseform</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-314 C 469,-311 486,-304 498,-293 C 530,-263 499,-228 534,-203 C 627,-140 1444,-179 1658,-190"
+         id="path509" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1657.7,-193.488 1668,-191 1658.4,-186.522 1657.7,-193.488 "
+         id="polygon511" />
+    </g>
+<!-- caseprint -->    <g
+       id="node31"
+       class="node">
+      <title
+         id="title514">caseprint</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1918,-326 1838,-326 1838,-290 1918,-290 1918,-326 "
+         id="polygon516" />
+      <text
+         x="1878"
+         y="-303.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text518">caseprint</text>
+    </g>
+<!-- case->caseprint -->    <g
+       id="edge86"
+       class="edge">
+      <title
+         id="title521">case->caseprint</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-333 C 476,-343 506,-357 534,-363 C 1030,-463 1649,-354 1827,-319"
+         id="path523" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1827.88,-322.393 1837,-317 1826.51,-315.529 1827.88,-322.393 "
+         id="polygon525" />
+    </g>
+<!-- casetask -->    <g
+       id="node32"
+       class="node">
+      <title
+         id="title528">casetask</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1916,-211 1840,-211 1840,-175 1916,-175 1916,-211 "
+         id="polygon530" />
+      <text
+         x="1878"
+         y="-188.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text532">casetask</text>
+    </g>
+<!-- case->casetask -->    <g
+       id="edge88"
+       class="edge">
+      <title
+         id="title535">case->casetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-315 C 469,-312 487,-306 498,-293 C 552,-231 473,-167 534,-111 C 571,-79 1366,-28 1415,-28 C 1415,-28 1415,-28 1555,-28 C 1659,-28 1692,-35 1780,-88 C 1814,-108 1843,-142 1860,-167"
+         id="path537" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1857.2,-169.1 1866,-175 1862.8,-164.9 1857.2,-169.1 "
+         id="polygon539" />
+    </g>
+<!-- casetasks -->    <g
+       id="node33"
+       class="node">
+      <title
+         id="title542">casetasks</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1457,-136 1373,-136 1373,-100 1457,-100 1457,-136 "
+         id="polygon544" />
+      <text
+         x="1415"
+         y="-113.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text546">casetasks</text>
+    </g>
+<!-- case->casetasks -->    <g
+       id="edge90"
+       class="edge">
+      <title
+         id="title549">case->casetasks</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 454,-315 C 469,-311 487,-305 498,-293 C 546,-239 480,-184 534,-137 C 579,-99 740,-118 799,-118 C 799,-118 799,-118 1021,-118 C 1144,-119 1287,-118 1363,-118"
+         id="path551" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1363,-121.5 1373,-118 1363,-114.5 1363,-121.5 "
+         id="polygon553" />
+    </g>
+<!-- case->logview -->    <g
+       id="edge118"
+       class="edge">
+      <title
+         id="title556">case->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 442,-336 C 458,-356 483,-390 498,-423 C 521,-470 493,-503 534,-536 C 581,-572 740,-558 799,-558 C 799,-558 799,-558 1021,-558 C 1278,-558 1385,-962 1409,-1073"
+         id="path558" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1405.61,-1073.88 1411,-1083 1412.47,-1072.51 1405.61,-1073.88 "
+         id="polygon560" />
+    </g>
+<!-- selmergecase -->    <g
+       id="node62"
+       class="node">
+      <title
+         id="title563">selmergecase</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1471,-556 1359,-556 1359,-520 1471,-520 1471,-556 "
+         id="polygon565" />
+      <text
+         x="1415"
+         y="-533.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text567">selmergecase</text>
+    </g>
+<!-- case->selmergecase -->    <g
+       id="edge120"
+       class="edge">
+      <title
+         id="title570">case->selmergecase</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 441,-336 C 457,-356 481,-391 498,-423 C 518,-459 501,-484 534,-509 C 630,-577 681,-528 799,-528 C 799,-528 799,-528 1021,-528 C 1137,-528 1270,-533 1348,-536"
+         id="path572" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1348,-539.5 1358,-536 1348,-532.5 1348,-539.5 "
+         id="polygon574" />
+    </g>
+<!-- selmergeforms -->    <g
+       id="node63"
+       class="node">
+      <title
+         id="title577">selmergeforms</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1939,-516 1817,-516 1817,-480 1939,-480 1939,-516 "
+         id="polygon579" />
+      <text
+         x="1878"
+         y="-493.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text581">selmergeforms</text>
+    </g>
+<!-- case->selmergeforms -->    <g
+       id="edge122"
+       class="edge">
+      <title
+         id="title584">case->selmergeforms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 440,-336 C 459,-360 494,-402 534,-426 C 640,-487 677,-498 799,-498 C 799,-498 799,-498 1555,-498 C 1641,-498 1741,-498 1806,-498"
+         id="path586" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1806,-501.5 1816,-498 1806,-494.5 1806,-501.5 "
+         id="polygon588" />
+    </g>
+<!-- unitview -->    <g
+       id="node70"
+       class="node">
+      <title
+         id="title591">unitview</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="2218,-341 2142,-341 2142,-305 2218,-305 2218,-341 "
+         id="polygon593" />
+      <text
+         x="2180"
+         y="-318.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text595">unitview</text>
+    </g>
+<!-- caseaccess->unitview -->    <g
+       id="edge76"
+       class="edge">
+      <title
+         id="title598">caseaccess->unitview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1602,-448 C 1718,-423 2014,-359 2131,-333"
+         id="path600" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="2131.88,-336.393 2141,-331 2130.51,-329.529 2131.88,-336.393 "
+         id="polygon602" />
+    </g>
+<!-- casecontact -->    <g
+       id="node28"
+       class="node">
+      <title
+         id="title605">casecontact</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1291,-306 1193,-306 1193,-270 1291,-270 1291,-306 "
+         id="polygon607" />
+      <text
+         x="1242"
+         y="-283.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text609">casecontact</text>
+    </g>
+<!-- casecontact->case -->    <g
+       id="edge92"
+       class="edge">
+      <title
+         id="title612">casecontact->case</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1192,-288 C 1077,-288 781,-291 534,-307 C 511,-309 485,-311 464,-314"
+         id="path614" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="463.602,-310.522 454,-315 464.299,-317.488 463.602,-310.522 "
+         id="polygon616" />
+    </g>
+<!-- casecontact->caseprint -->    <g
+       id="edge94"
+       class="edge">
+      <title
+         id="title619">casecontact->caseprint</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1292,-293 C 1312,-295 1336,-297 1358,-299 C 1546,-312 1593,-317 1780,-313 C 1795,-312 1812,-311 1827,-311"
+         id="path621" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1827.4,-314.478 1837,-310 1826.7,-307.512 1827.4,-314.478 "
+         id="polygon623" />
+    </g>
+<!-- casecontact->casetask -->    <g
+       id="edge96"
+       class="edge">
+      <title
+         id="title626">casecontact->casetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1292,-283 C 1387,-274 1602,-252 1780,-218 C 1796,-215 1814,-211 1829,-207"
+         id="path628" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1830.43,-210.226 1839,-204 1828.42,-203.521 1830.43,-210.226 "
+         id="polygon630" />
+    </g>
+<!-- casecontact->casetasks -->    <g
+       id="edge98"
+       class="edge">
+      <title
+         id="title633">casecontact->casetasks</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1261,-270 C 1292,-239 1354,-178 1390,-143"
+         id="path635" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1392.4,-145.546 1397,-136 1387.45,-140.596 1392.4,-145.546 "
+         id="polygon637" />
+    </g>
+<!-- followup -->    <g
+       id="node41"
+       class="node">
+      <title
+         id="title640">followup</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1748,-306 1670,-306 1670,-270 1748,-270 1748,-306 "
+         id="polygon642" />
+      <text
+         x="1709"
+         y="-283.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text644">followup</text>
+    </g>
+<!-- casecontact->followup -->    <g
+       id="edge100"
+       class="edge">
+      <title
+         id="title647">casecontact->followup</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1292,-288 C 1381,-288 1570,-288 1660,-288"
+         id="path649" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1660,-291.5 1670,-288 1660,-284.5 1660,-291.5 "
+         id="polygon651" />
+    </g>
+<!-- casecontact->logview -->    <g
+       id="edge102"
+       class="edge">
+      <title
+         id="title654">casecontact->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1249,-306 C 1264,-348 1301,-455 1322,-547 C 1370,-745 1401,-990 1412,-1073"
+         id="path656" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1408.52,-1073.4 1413,-1083 1415.49,-1072.7 1408.52,-1073.4 "
+         id="polygon658" />
+    </g>
+<!-- casecontact->selmergecase -->    <g
+       id="edge108"
+       class="edge">
+      <title
+         id="title661">casecontact->selmergecase</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1255,-306 C 1285,-350 1362,-461 1397,-511"
+         id="path663" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1394.54,-513.621 1403,-520 1400.37,-509.738 1394.54,-513.621 "
+         id="polygon665" />
+    </g>
+<!-- casecontact->selmergeforms -->    <g
+       id="edge110"
+       class="edge">
+      <title
+         id="title668">casecontact->selmergeforms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1292,-304 C 1406,-342 1686,-435 1813,-477"
+         id="path670" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1812.42,-480.479 1823,-480 1814.43,-473.774 1812.42,-480.479 "
+         id="polygon672" />
+    </g>
+<!-- casecontacts->case -->    <g
+       id="edge104"
+       class="edge">
+      <title
+         id="title675">casecontacts->case</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 571,-246 C 558,-251 545,-257 534,-264 C 516,-276 518,-287 498,-300 C 488,-306 475,-311 464,-315"
+         id="path677" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="462.573,-311.774 454,-318 464.584,-318.479 462.573,-311.774 "
+         id="polygon679" />
+    </g>
+<!-- casecontacts->casecontact -->    <g
+       id="edge106"
+       class="edge">
+      <title
+         id="title682">casecontacts->casecontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 662,-225 C 680,-225 700,-224 718,-223 C 900,-219 949,-216 1126,-253 C 1145,-257 1165,-262 1182,-268"
+         id="path684" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1181.42,-271.479 1192,-271 1183.43,-264.774 1181.42,-271.479 "
+         id="polygon686" />
+    </g>
+<!-- newcontact -->    <g
+       id="node53"
+       class="node">
+      <title
+         id="title689">newcontact</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="847,-266 751,-266 751,-230 847,-230 847,-266 "
+         id="polygon691" />
+      <text
+         x="799"
+         y="-243.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text693">newcontact</text>
+    </g>
+<!-- casecontacts->newcontact -->    <g
+       id="edge112"
+       class="edge">
+      <title
+         id="title696">casecontacts->newcontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 662,-234 C 687,-237 715,-239 740,-242"
+         id="path698" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="739.701,-245.488 750,-243 740.398,-238.522 739.701,-245.488 "
+         id="polygon700" />
+    </g>
+<!-- caseform->caseprint -->    <g
+       id="edge114"
+       class="edge">
+      <title
+         id="title703">caseform->caseprint</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1736,-211 C 1764,-231 1811,-262 1843,-284"
+         id="path705" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1840.9,-286.8 1851,-290 1845.1,-281.2 1840.9,-286.8 "
+         id="polygon707" />
+    </g>
+<!-- caseform->casetask -->    <g
+       id="edge116"
+       class="edge">
+      <title
+         id="title710">caseform->casetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1750,-193 C 1774,-193 1804,-193 1829,-193"
+         id="path712" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1829,-196.5 1839,-193 1829,-189.5 1829,-196.5 "
+         id="polygon714" />
+    </g>
+<!-- taskedit -->    <g
+       id="node67"
+       class="node">
+      <title
+         id="title717">taskedit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="2060,-211 1988,-211 1988,-175 2060,-175 2060,-211 "
+         id="polygon719" />
+      <text
+         x="2024"
+         y="-188.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text721">taskedit</text>
+    </g>
+<!-- casetask->taskedit -->    <g
+       id="edge126"
+       class="edge">
+      <title
+         id="title724">casetask->taskedit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1917,-193 C 1936,-193 1958,-193 1978,-193"
+         id="path726" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1978,-196.5 1988,-193 1978,-189.5 1978,-196.5 "
+         id="polygon728" />
+    </g>
+<!-- notetask -->    <g
+       id="node54"
+       class="node">
+      <title
+         id="title731">notetask</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1747,-131 1671,-131 1671,-95 1747,-95 1747,-131 "
+         id="polygon733" />
+      <text
+         x="1709"
+         y="-108.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text735">notetask</text>
+    </g>
+<!-- casetasks->notetask -->    <g
+       id="edge244"
+       class="edge">
+      <title
+         id="title738">casetasks->notetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1457,-117 C 1511,-116 1603,-115 1660,-114"
+         id="path740" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1660,-117.5 1670,-114 1660,-110.5 1660,-117.5 "
+         id="polygon742" />
+    </g>
+<!-- taskaction -->    <g
+       id="node66"
+       class="node">
+      <title
+         id="title745">taskaction</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1599,-171 1511,-171 1511,-135 1599,-135 1599,-171 "
+         id="polygon747" />
+      <text
+         x="1555"
+         y="-148.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text749">taskaction</text>
+    </g>
+<!-- casetasks->taskaction -->    <g
+       id="edge124"
+       class="edge">
+      <title
+         id="title752">casetasks->taskaction</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1457,-129 C 1471,-132 1486,-136 1500,-140"
+         id="path754" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1499.51,-143.471 1510,-142 1500.88,-136.607 1499.51,-143.471 "
+         id="polygon756" />
+    </g>
+<!-- contactvis -->    <g
+       id="node34"
+       class="node">
+      <title
+         id="title759">contactvis</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="652,-36 564,-36 564,-1.06581e-14 652,0 652,-36 "
+         id="polygon761" />
+      <text
+         x="608"
+         y="-13.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text763">contactvis</text>
+    </g>
+<!-- dataimp -->    <g
+       id="node35"
+       class="node">
+      <title
+         id="title766">dataimp</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="287,-331 213,-331 213,-295 287,-295 287,-331 "
+         id="polygon768" />
+      <text
+         x="250"
+         y="-308.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text770">dataimp</text>
+    </g>
+<!-- dataimp_editfield -->    <g
+       id="node36"
+       class="node">
+      <title
+         id="title773">dataimp_editfield</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="497,-286 357,-286 357,-250 497,-250 497,-286 "
+         id="polygon775" />
+      <text
+         x="427"
+         y="-263.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text777">dataimp_editfield</text>
+    </g>
+<!-- dataimp->dataimp_editfield -->    <g
+       id="edge130"
+       class="edge">
+      <title
+         id="title780">dataimp->dataimp_editfield</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 287,-304 C 304,-300 326,-294 346,-289"
+         id="path782" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="347.427,-292.226 356,-286 345.416,-285.521 347.427,-292.226 "
+         id="polygon784" />
+    </g>
+<!-- dupepersons -->    <g
+       id="node37"
+       class="node">
+      <title
+         id="title787">dupepersons</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="853,-1236 745,-1236 745,-1200 853,-1200 853,-1236 "
+         id="polygon789" />
+      <text
+         x="799"
+         y="-1213.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text791">dupepersons</text>
+    </g>
+<!-- mergeperson -->    <g
+       id="node50"
+       class="node">
+      <title
+         id="title794">mergeperson</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1076,-1189 966,-1189 966,-1153 1076,-1153 1076,-1189 "
+         id="polygon796" />
+      <text
+         x="1021"
+         y="-1166.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text798">mergeperson</text>
+    </g>
+<!-- dupepersons->mergeperson -->    <g
+       id="edge132"
+       class="edge">
+      <title
+         id="title801">dupepersons->mergeperson</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 853,-1207 C 884,-1201 923,-1192 956,-1185"
+         id="path803" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="956.881,-1188.39 966,-1183 955.508,-1181.53 956.881,-1188.39 "
+         id="polygon805" />
+    </g>
+<!-- dupepersons_config -->    <g
+       id="node38"
+       class="node">
+      <title
+         id="title808">dupepersons_config</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="879,-1366 719,-1366 719,-1330 879,-1330 879,-1366 "
+         id="polygon810" />
+      <text
+         x="799"
+         y="-1343.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text812">dupepersons_config</text>
+    </g>
+<!-- dupepersons_config_edit -->    <g
+       id="node39"
+       class="node">
+      <title
+         id="title815">dupepersons_config_edit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1119,-1366 923,-1366 923,-1330 1119,-1330 1119,-1366 "
+         id="polygon817" />
+      <text
+         x="1021"
+         y="-1343.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text819">dupepersons_config_edit</text>
+    </g>
+<!-- dupepersons_config->dupepersons_config_edit -->    <g
+       id="edge242"
+       class="edge">
+      <title
+         id="title822">dupepersons_config->dupepersons_config_edit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 880,-1348 C 891,-1348 901,-1348 912,-1348"
+         id="path824" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="912,-1351.5 922,-1348 912,-1344.5 912,-1351.5 "
+         id="polygon826" />
+    </g>
+<!-- export -->    <g
+       id="node40"
+       class="node">
+      <title
+         id="title829">export</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="281,-586 219,-586 219,-550 281,-550 281,-586 "
+         id="polygon831" />
+      <text
+         x="250"
+         y="-563.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text833">export</text>
+    </g>
+<!-- followup->caseprint -->    <g
+       id="edge134"
+       class="edge">
+      <title
+         id="title836">followup->caseprint</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1748,-293 C 1772,-296 1802,-299 1827,-302"
+         id="path838" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1826.7,-305.488 1837,-303 1827.4,-298.522 1826.7,-305.488 "
+         id="polygon840" />
+    </g>
+<!-- followup->casetask -->    <g
+       id="edge136"
+       class="edge">
+      <title
+         id="title843">followup->casetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1748,-274 C 1759,-269 1770,-264 1780,-259 C 1804,-247 1828,-231 1847,-217"
+         id="path845" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1849.1,-219.8 1855,-211 1844.9,-214.2 1849.1,-219.8 "
+         id="polygon847" />
+    </g>
+<!-- login -->    <g
+       id="node42"
+       class="node">
+      <title
+         id="title850">login</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="54,-1001 1.42109e-14,-1001 3.55271e-15,-965 54,-965 54,-1001 "
+         id="polygon852" />
+      <text
+         x="27"
+         y="-978.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text854">login</text>
+    </g>
+<!-- main -->    <g
+       id="node44"
+       class="node">
+      <title
+         id="title857">main</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="144,-466 90,-466 90,-430 144,-430 144,-466 "
+         id="polygon859" />
+      <text
+         x="117"
+         y="-443.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text861">main</text>
+    </g>
+<!-- login->main -->    <g
+       id="edge138"
+       class="edge">
+      <title
+         id="title864">login->main</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 30,-965 C 43,-886 96,-573 112,-476"
+         id="path866" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="115.471,-476.492 114,-466 108.607,-475.119 115.471,-476.492 "
+         id="polygon868" />
+    </g>
+<!-- useredit -->    <g
+       id="node72"
+       class="node">
+      <title
+         id="title871">useredit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1058,-1456 984,-1456 984,-1420 1058,-1420 1058,-1456 "
+         id="polygon873" />
+      <text
+         x="1021"
+         y="-1433.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text875">useredit</text>
+    </g>
+<!-- login->useredit -->    <g
+       id="edge140"
+       class="edge">
+      <title
+         id="title878">login->useredit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 30,-1001 C 48,-1095 136,-1518 250,-1518 C 250,-1518 250,-1518 608,-1518 C 730,-1518 763,-1532 880,-1503 C 916,-1494 954,-1475 982,-1461"
+         id="path880" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="983.958,-1463.92 991,-1456 980.559,-1457.8 983.958,-1463.92 "
+         id="polygon882" />
+    </g>
+<!-- main->admin -->    <g
+       id="edge142"
+       class="edge">
+      <title
+         id="title885">main->admin</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 130,-466 C 141,-481 159,-501 180,-513 C 236,-543 272,-501 320,-543 C 399,-611 420,-742 425,-800"
+         id="path887" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="421.522,-800.398 426,-810 428.488,-799.701 421.522,-800.398 "
+         id="polygon889" />
+    </g>
+<!-- main->bulletin_detail -->    <g
+       id="edge144"
+       class="edge">
+      <title
+         id="title892">main->bulletin_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 117,-466 C 119,-540 129,-813 180,-879 C 181,-881 182,-882 184,-883"
+         id="path894" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="182.169,-886.049 192,-890 186.779,-880.781 182.169,-886.049 "
+         id="polygon896" />
+    </g>
+<!-- main->dataimp -->    <g
+       id="edge146"
+       class="edge">
+      <title
+         id="title899">main->dataimp</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 134,-430 C 147,-417 165,-398 180,-383 C 194,-368 211,-351 225,-338"
+         id="path901" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="227.404,-340.546 232,-331 222.454,-335.596 227.404,-340.546 "
+         id="polygon903" />
+    </g>
+<!-- main->export -->    <g
+       id="edge150"
+       class="edge">
+      <title
+         id="title906">main->export</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 126,-466 C 136,-486 155,-519 180,-539 C 189,-546 199,-551 209,-556"
+         id="path908" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="208.416,-559.479 219,-559 210.427,-552.774 208.416,-559.479 "
+         id="polygon910" />
+    </g>
+<!-- newcase -->    <g
+       id="node52"
+       class="node">
+      <title
+         id="title913">newcase</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="288,-426 212,-426 212,-390 288,-390 288,-426 "
+         id="polygon915" />
+      <text
+         x="250"
+         y="-403.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text917">newcase</text>
+    </g>
+<!-- main->newcase -->    <g
+       id="edge152"
+       class="edge">
+      <title
+         id="title920">main->newcase</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 144,-440 C 160,-435 182,-428 201,-423"
+         id="path922" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="202.427,-426.226 211,-420 200.416,-419.521 202.427,-426.226 "
+         id="polygon924" />
+    </g>
+<!-- reports -->    <g
+       id="node57"
+       class="node">
+      <title
+         id="title927">reports</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="283,-152 217,-152 217,-116 283,-116 283,-152 "
+         id="polygon929" />
+      <text
+         x="250"
+         y="-129.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text931">reports</text>
+    </g>
+<!-- main->reports -->    <g
+       id="edge154"
+       class="edge">
+      <title
+         id="title934">main->reports</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 119,-430 C 125,-373 146,-205 180,-163 C 187,-155 197,-149 206,-145"
+         id="path936" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="208.015,-147.964 216,-141 205.415,-141.464 208.015,-147.964 "
+         id="polygon938" />
+    </g>
+<!-- report_table -->    <g
+       id="node58"
+       class="node">
+      <title
+         id="title941">report_table</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="479,-159 375,-159 375,-123 479,-123 479,-159 "
+         id="polygon943" />
+      <text
+         x="427"
+         y="-136.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text945">report_table</text>
+    </g>
+<!-- main->report_table -->    <g
+       id="edge156"
+       class="edge">
+      <title
+         id="title948">main->report_table</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 119,-430 C 125,-377 146,-227 180,-193 C 188,-187 294,-166 365,-153"
+         id="path950" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="365.881,-156.393 375,-151 364.508,-149.529 365.881,-156.393 "
+         id="polygon952" />
+    </g>
+<!-- search -->    <g
+       id="node60"
+       class="node">
+      <title
+         id="title955">search</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="458,-466 396,-466 396,-430 458,-430 458,-466 "
+         id="polygon957" />
+      <text
+         x="427"
+         y="-443.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text959">search</text>
+    </g>
+<!-- main->search -->    <g
+       id="edge158"
+       class="edge">
+      <title
+         id="title962">main->search</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 144,-448 C 199,-448 320,-448 385,-448"
+         id="path964" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="385,-451.5 395,-448 385,-444.5 385,-451.5 "
+         id="polygon966" />
+    </g>
+<!-- selcontactvis -->    <g
+       id="node61"
+       class="node">
+      <title
+         id="title969">selcontactvis</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="481,-62 373,-62 373,-26 481,-26 481,-62 "
+         id="polygon971" />
+      <text
+         x="427"
+         y="-39.900002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text973">selcontactvis</text>
+    </g>
+<!-- main->selcontactvis -->    <g
+       id="edge160"
+       class="edge">
+      <title
+         id="title976">main->selcontactvis</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 119,-430 C 125,-356 152,-85 180,-59 C 229,-16 307,-18 363,-28"
+         id="path978" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="362.508,-31.4708 373,-30 363.881,-24.6067 362.508,-31.4708 "
+         id="polygon980" />
+    </g>
+<!-- selprintforms -->    <g
+       id="node64"
+       class="node">
+      <title
+         id="title983">selprintforms</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="305,-506 195,-506 195,-470 305,-470 305,-506 "
+         id="polygon985" />
+      <text
+         x="250"
+         y="-483.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text987">selprintforms</text>
+    </g>
+<!-- main->selprintforms -->    <g
+       id="edge162"
+       class="edge">
+      <title
+         id="title990">main->selprintforms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 144,-456 C 156,-459 170,-464 184,-468"
+         id="path992" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="183.416,-471.479 194,-471 185.427,-464.774 183.416,-471.479 "
+         id="polygon994" />
+    </g>
+<!-- syn_detail -->    <g
+       id="node65"
+       class="node">
+      <title
+         id="title997">syn_detail</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="295,-102 205,-102 205,-66 295,-66 295,-102 "
+         id="polygon999" />
+      <text
+         x="250"
+         y="-79.900002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1001">syn_detail</text>
+    </g>
+<!-- main->syn_detail -->    <g
+       id="edge164"
+       class="edge">
+      <title
+         id="title1004">main->syn_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 118,-430 C 122,-367 139,-159 180,-109 C 184,-104 190,-100 195,-97"
+         id="path1006" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="197.015,-99.9636 205,-93 194.415,-93.4643 197.015,-99.9636 "
+         id="polygon1008" />
+    </g>
+<!-- tasks -->    <g
+       id="node68"
+       class="node">
+      <title
+         id="title1011">tasks</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1442,-86 1388,-86 1388,-50 1442,-50 1442,-86 "
+         id="polygon1013" />
+      <text
+         x="1415"
+         y="-63.900002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1015">tasks</text>
+    </g>
+<!-- main->tasks -->    <g
+       id="edge166"
+       class="edge">
+      <title
+         id="title1018">main->tasks</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 119,-430 C 124,-397 140,-328 180,-288 C 288,-182 390,-272 498,-166 C 529,-137 500,-103 534,-77 C 582,-43 740,-58 799,-58 C 799,-58 799,-58 1021,-58 C 1153,-58 1308,-63 1378,-67"
+         id="path1020" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1378,-70.5001 1388,-67 1378,-63.5001 1378,-70.5001 "
+         id="polygon1022" />
+    </g>
+<!-- tools -->    <g
+       id="node69"
+       class="node">
+      <title
+         id="title1025">tools</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="635,-1326 581,-1326 581,-1290 635,-1290 635,-1326 "
+         id="polygon1027" />
+      <text
+         x="608"
+         y="-1303.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1029">tools</text>
+    </g>
+<!-- main->tools -->    <g
+       id="edge168"
+       class="edge">
+      <title
+         id="title1032">main->tools</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 124,-466 C 135,-494 157,-548 180,-593 C 323,-871 529,-1189 590,-1281"
+         id="path1034" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="587.541,-1283.62 596,-1290 593.365,-1279.74 587.541,-1283.62 "
+         id="polygon1036" />
+    </g>
+<!-- demog_imp -->    <g
+       id="node149"
+       class="node">
+      <title
+         id="title1039">demog_imp</title>
+      <ellipse
+         style="fill:none;stroke:#000000"
+         cx="250"
+         cy="-976"
+         rx="70.183098"
+         ry="18"
+         id="ellipse1041"
+         sodipodi:cx="250"
+         sodipodi:cy="-976"
+         sodipodi:rx="70.183098"
+         sodipodi:ry="18" />
+      <text
+         x="250"
+         y="-971.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1043">demog_imp</text>
+    </g>
+<!-- main->demog_imp -->    <g
+       id="edge148"
+       class="edge">
+      <title
+         id="title1046">main->demog_imp</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 118,-466 C 124,-551 149,-912 180,-951 C 183,-954 185,-956 188,-958"
+         id="path1048" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="186.738,-961.365 197,-964 190.621,-955.541 186.738,-961.365 "
+         id="polygon1050" />
+    </g>
+<!-- manualdupe -->    <g
+       id="node45"
+       class="node">
+      <title
+         id="title1053">manualdupe</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="851,-1186 747,-1186 747,-1150 851,-1150 851,-1186 "
+         id="polygon1055" />
+      <text
+         x="799"
+         y="-1163.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1057">manualdupe</text>
+    </g>
+<!-- manualdupe->mergeperson -->    <g
+       id="edge170"
+       class="edge">
+      <title
+         id="title1060">manualdupe->mergeperson</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 851,-1169 C 883,-1170 923,-1170 956,-1170"
+         id="path1062" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="956,-1173.5 966,-1170 956,-1166.5 956,-1173.5 "
+         id="polygon1064" />
+    </g>
+<!-- mergecase -->    <g
+       id="node46"
+       class="node">
+      <title
+         id="title1067">mergecase</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1601,-556 1509,-556 1509,-520 1601,-520 1601,-556 "
+         id="polygon1069" />
+      <text
+         x="1555"
+         y="-533.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1071">mergecase</text>
+    </g>
+<!-- mergecase_detail -->    <g
+       id="node47"
+       class="node">
+      <title
+         id="title1074">mergecase_detail</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1779,-556 1639,-556 1639,-520 1779,-520 1779,-556 "
+         id="polygon1076" />
+      <text
+         x="1709"
+         y="-533.90002"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1078">mergecase_detail</text>
+    </g>
+<!-- mergecase->mergecase_detail -->    <g
+       id="edge174"
+       class="edge">
+      <title
+         id="title1081">mergecase->mergecase_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1602,-538 C 1610,-538 1619,-538 1628,-538"
+         id="path1083" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1628,-541.5 1638,-538 1628,-534.5 1628,-541.5 "
+         id="polygon1085" />
+    </g>
+<!-- mergecase_detail->selmergeforms -->    <g
+       id="edge172"
+       class="edge">
+      <title
+         id="title1088">mergecase_detail->selmergeforms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1780,-521 C 1789,-519 1797,-517 1806,-514"
+         id="path1090" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1806.88,-517.393 1816,-512 1805.51,-510.529 1806.88,-517.393 "
+         id="polygon1092" />
+    </g>
+<!-- mergeform -->    <g
+       id="node48"
+       class="node">
+      <title
+         id="title1095">mergeform</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="2071,-516 1977,-516 1977,-480 2071,-480 2071,-516 "
+         id="polygon1097" />
+      <text
+         x="2024"
+         y="-493.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1099">mergeform</text>
+    </g>
+<!-- mergeform_detail -->    <g
+       id="node49"
+       class="node">
+      <title
+         id="title1102">mergeform_detail</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="2252,-516 2108,-516 2108,-480 2252,-480 2252,-516 "
+         id="polygon1104" />
+      <text
+         x="2180"
+         y="-493.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1106">mergeform_detail</text>
+    </g>
+<!-- mergeform->mergeform_detail -->    <g
+       id="edge176"
+       class="edge">
+      <title
+         id="title1109">mergeform->mergeform_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 2072,-498 C 2080,-498 2089,-498 2098,-498"
+         id="path1111" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="2098,-501.5 2108,-498 2098,-494.5 2098,-501.5 "
+         id="polygon1113" />
+    </g>
+<!-- mergeperson_detail -->    <g
+       id="node51"
+       class="node">
+      <title
+         id="title1116">mergeperson_detail</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1321,-1189 1163,-1189 1163,-1153 1321,-1153 1321,-1189 "
+         id="polygon1118" />
+      <text
+         x="1242"
+         y="-1166.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1120">mergeperson_detail</text>
+    </g>
+<!-- mergeperson->mergeperson_detail -->    <g
+       id="edge180"
+       class="edge">
+      <title
+         id="title1123">mergeperson->mergeperson_detail</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1076,-1171 C 1099,-1171 1126,-1171 1152,-1171"
+         id="path1125" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1152,-1174.5 1162,-1171 1152,-1167.5 1152,-1174.5 "
+         id="polygon1127" />
+    </g>
+<!-- mergeperson_detail->selmergecase -->    <g
+       id="edge178"
+       class="edge">
+      <title
+         id="title1130">mergeperson_detail->selmergecase</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1315,-1153 C 1317,-1151 1320,-1149 1322,-1146 C 1399,-1057 1412,-674 1415,-566"
+         id="path1132" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1418.5,-566 1415,-556 1411.5,-566 1418.5,-566 "
+         id="polygon1134" />
+    </g>
+<!-- newcase->case -->    <g
+       id="edge182"
+       class="edge">
+      <title
+         id="title1137">newcase->case</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 286,-390 C 317,-374 360,-352 391,-336"
+         id="path1139" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="392.283,-339.26 400,-332 389.44,-332.863 392.283,-339.26 "
+         id="polygon1141" />
+    </g>
+<!-- newcase->result -->    <g
+       id="edge184"
+       class="edge">
+      <title
+         id="title1144">newcase->result</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 289,-404 C 420,-392 850,-353 982,-342"
+         id="path1146" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="982.398,-345.478 992,-341 981.701,-338.512 982.398,-345.478 "
+         id="polygon1148" />
+    </g>
+<!-- newcontact->casecontact -->    <g
+       id="edge186"
+       class="edge">
+      <title
+         id="title1151">newcontact->casecontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 848,-252 C 911,-257 1027,-267 1126,-277 C 1144,-279 1164,-280 1182,-282"
+         id="path1153" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1181.7,-285.488 1192,-283 1182.4,-278.522 1181.7,-285.488 "
+         id="polygon1155" />
+    </g>
+<!-- newcontact->result -->    <g
+       id="edge188"
+       class="edge">
+      <title
+         id="title1158">newcontact->result</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 844,-266 C 885,-283 944,-307 983,-322"
+         id="path1160" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="981.44,-325.137 992,-326 984.283,-318.74 981.44,-325.137 "
+         id="polygon1162" />
+    </g>
+<!-- notetask->taskedit -->    <g
+       id="edge128"
+       class="edge">
+      <title
+         id="title1165">notetask->taskedit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1748,-122 C 1794,-132 1873,-150 1940,-168 C 1953,-172 1966,-176 1978,-179"
+         id="path1167" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1977.42,-182.479 1988,-182 1979.43,-175.774 1977.42,-182.479 "
+         id="polygon1169" />
+    </g>
+<!-- prefsedit -->    <g
+       id="node55"
+       class="node">
+      <title
+         id="title1172">prefsedit</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="838,-1496 760,-1496 760,-1460 838,-1460 838,-1496 "
+         id="polygon1174" />
+      <text
+         x="799"
+         y="-1473.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1176">prefsedit</text>
+    </g>
+<!-- printforms -->    <g
+       id="node56"
+       class="node">
+      <title
+         id="title1179">printforms</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="472,-516 382,-516 382,-480 472,-480 472,-516 "
+         id="polygon1181" />
+      <text
+         x="427"
+         y="-493.89999"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1183">printforms</text>
+    </g>
+<!-- reports->report_table -->    <g
+       id="edge190"
+       class="edge">
+      <title
+         id="title1186">reports->report_table</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 284,-135 C 307,-136 338,-138 365,-139"
+         id="path1188" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="365,-142.5 375,-139 365,-135.5 365,-142.5 "
+         id="polygon1190" />
+    </g>
+<!-- reports->selcontactvis -->    <g
+       id="edge192"
+       class="edge">
+      <title
+         id="title1193">reports->selcontactvis</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 284,-124 C 296,-120 309,-115 320,-109 C 345,-98 372,-81 392,-68"
+         id="path1195" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="394.621,-70.4592 401,-62 390.738,-64.6349 394.621,-70.4592 "
+         id="polygon1197" />
+    </g>
+<!-- result->case -->    <g
+       id="edge194"
+       class="edge">
+      <title
+         id="title1200">result->case</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 992,-335 C 934,-329 797,-317 682,-313 C 617,-310 600,-311 534,-313 C 511,-314 485,-315 464,-316"
+         id="path1202" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="464,-312.5 454,-316 464,-319.5 464,-312.5 "
+         id="polygon1204" />
+    </g>
+<!-- result->casecontact -->    <g
+       id="edge196"
+       class="edge">
+      <title
+         id="title1207">result->casecontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1050,-331 C 1083,-324 1140,-311 1182,-301"
+         id="path1209" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1182.88,-304.393 1192,-299 1181.51,-297.529 1182.88,-304.393 "
+         id="polygon1211" />
+    </g>
+<!-- search->result -->    <g
+       id="edge198"
+       class="edge">
+      <title
+         id="title1214">search->result</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 459,-446 C 533,-442 726,-428 880,-389 C 916,-380 955,-366 983,-354"
+         id="path1216" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="984.283,-357.26 992,-350 981.44,-350.863 984.283,-357.26 "
+         id="polygon1218" />
+    </g>
+<!-- selcontactvis->contactvis -->    <g
+       id="edge200"
+       class="edge">
+      <title
+         id="title1221">selcontactvis->contactvis</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 481,-36 C 505,-33 531,-29 554,-26"
+         id="path1223" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="554.398,-29.4778 564,-25 553.701,-22.5125 554.398,-29.4778 "
+         id="polygon1225" />
+    </g>
+<!-- selmergecase->mergecase -->    <g
+       id="edge202"
+       class="edge">
+      <title
+         id="title1228">selmergecase->mergecase</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1472,-538 C 1481,-538 1489,-538 1498,-538"
+         id="path1230" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1498,-541.5 1508,-538 1498,-534.5 1498,-541.5 "
+         id="polygon1232" />
+    </g>
+<!-- selmergeforms->mergeform -->    <g
+       id="edge204"
+       class="edge">
+      <title
+         id="title1235">selmergeforms->mergeform</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1940,-498 C 1948,-498 1957,-498 1966,-498"
+         id="path1237" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1966,-501.5 1976,-498 1966,-494.5 1966,-501.5 "
+         id="polygon1239" />
+    </g>
+<!-- selprintforms->printforms -->    <g
+       id="edge206"
+       class="edge">
+      <title
+         id="title1242">selprintforms->printforms</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 306,-491 C 327,-492 350,-493 371,-495"
+         id="path1244" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="371,-498.5 381,-495 371,-491.5 371,-498.5 "
+         id="polygon1246" />
+    </g>
+<!-- taskaction->case -->    <g
+       id="edge208"
+       class="edge">
+      <title
+         id="title1249">taskaction->case</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1510,-151 C 1324,-141 613,-108 534,-167 C 488,-203 538,-250 498,-293 C 489,-302 476,-308 464,-312"
+         id="path1251" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="462.573,-308.774 454,-315 464.584,-315.479 462.573,-308.774 "
+         id="polygon1253" />
+    </g>
+<!-- taskaction->casecontact -->    <g
+       id="edge210"
+       class="edge">
+      <title
+         id="title1256">taskaction->casecontact</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1513,-171 C 1473,-188 1412,-214 1358,-237 C 1336,-246 1313,-257 1292,-266"
+         id="path1258" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1290.72,-262.74 1283,-270 1293.56,-269.137 1290.72,-262.74 "
+         id="polygon1260" />
+    </g>
+<!-- taskaction->caseform -->    <g
+       id="edge212"
+       class="edge">
+      <title
+         id="title1263">taskaction->caseform</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1600,-165 C 1618,-169 1639,-175 1658,-180"
+         id="path1265" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1657.51,-183.471 1668,-182 1658.88,-176.607 1657.51,-183.471 "
+         id="polygon1267" />
+    </g>
+<!-- taskaction->casetask -->    <g
+       id="edge214"
+       class="edge">
+      <title
+         id="title1270">taskaction->casetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1600,-154 C 1646,-155 1719,-159 1780,-168 C 1796,-171 1814,-175 1829,-179"
+         id="path1272" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1828.42,-182.479 1839,-182 1830.43,-175.774 1828.42,-182.479 "
+         id="polygon1274" />
+    </g>
+<!-- taskaction->followup -->    <g
+       id="edge216"
+       class="edge">
+      <title
+         id="title1277">taskaction->followup</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1576,-171 C 1603,-195 1650,-236 1680,-263"
+         id="path1279" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1678.17,-266.049 1688,-270 1682.78,-260.781 1678.17,-266.049 "
+         id="polygon1281" />
+    </g>
+<!-- taskaction->notetask -->    <g
+       id="edge246"
+       class="edge">
+      <title
+         id="title1284">taskaction->notetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1600,-141 C 1619,-137 1641,-131 1660,-126"
+         id="path1286" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1661.43,-129.226 1670,-123 1659.42,-122.521 1661.43,-129.226 "
+         id="polygon1288" />
+    </g>
+<!-- taskedit->unitview -->    <g
+       id="edge250"
+       class="edge">
+      <title
+         id="title1291">taskedit->unitview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 2046,-211 C 2074,-234 2120,-273 2150,-298"
+         id="path1293" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="2148.17,-301.049 2158,-305 2152.78,-295.781 2148.17,-301.049 "
+         id="polygon1295" />
+    </g>
+<!-- tasks->notetask -->    <g
+       id="edge248"
+       class="edge">
+      <title
+         id="title1298">tasks->notetask</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1442,-72 C 1492,-80 1598,-96 1660,-106"
+         id="path1300" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1659.7,-109.488 1670,-107 1660.4,-102.522 1659.7,-109.488 "
+         id="polygon1302" />
+    </g>
+<!-- tasks->taskaction -->    <g
+       id="edge218"
+       class="edge">
+      <title
+         id="title1305">tasks->taskaction</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 1442,-79 C 1452,-83 1463,-88 1472,-93 C 1491,-104 1510,-117 1525,-129"
+         id="path1307" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1522.9,-131.8 1533,-135 1527.1,-126.2 1522.9,-131.8 "
+         id="polygon1309" />
+    </g>
+<!-- tools->dupepersons -->    <g
+       id="edge220"
+       class="edge">
+      <title
+         id="title1312">tools->dupepersons</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 634,-1290 C 656,-1275 689,-1256 718,-1243 C 724,-1240 730,-1238 736,-1236"
+         id="path1314" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="737.283,-1239.26 745,-1232 734.44,-1232.86 737.283,-1239.26 "
+         id="polygon1316" />
+    </g>
+<!-- tools->dupepersons_config -->    <g
+       id="edge240"
+       class="edge">
+      <title
+         id="title1319">tools->dupepersons_config</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 635,-1314 C 655,-1318 682,-1324 708,-1329"
+         id="path1321" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="707.508,-1332.47 718,-1331 708.881,-1325.61 707.508,-1332.47 "
+         id="polygon1323" />
+    </g>
+<!-- tools->logview -->    <g
+       id="edge222"
+       class="edge">
+      <title
+         id="title1326">tools->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 635,-1311 C 713,-1319 944,-1338 1126,-1293 C 1221,-1269 1244,-1253 1322,-1196 C 1349,-1176 1376,-1148 1394,-1127"
+         id="path1328" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1396.8,-1129.1 1400,-1119 1391.2,-1124.9 1396.8,-1129.1 "
+         id="polygon1330" />
+    </g>
+<!-- tools->manualdupe -->    <g
+       id="edge224"
+       class="edge">
+      <title
+         id="title1333">tools->manualdupe</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 621,-1290 C 639,-1264 676,-1219 718,-1193 C 724,-1190 731,-1186 737,-1184"
+         id="path1335" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="739.015,-1186.96 747,-1180 736.415,-1180.46 739.015,-1186.96 "
+         id="polygon1337" />
+    </g>
+<!-- tools->prefsedit -->    <g
+       id="edge226"
+       class="edge">
+      <title
+         id="title1340">tools->prefsedit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 618,-1326 C 635,-1356 671,-1415 718,-1449 C 727,-1456 738,-1461 749,-1465"
+         id="path1342" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="748.415,-1468.54 759,-1469 751.015,-1462.04 748.415,-1468.54 "
+         id="polygon1344" />
+    </g>
+<!-- useradmin -->    <g
+       id="node71"
+       class="node">
+      <title
+         id="title1347">useradmin</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="844,-1416 754,-1416 754,-1380 844,-1380 844,-1416 "
+         id="polygon1349" />
+      <text
+         x="799"
+         y="-1393.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1351">useradmin</text>
+    </g>
+<!-- tools->useradmin -->    <g
+       id="edge228"
+       class="edge">
+      <title
+         id="title1354">tools->useradmin</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 635,-1326 C 657,-1340 689,-1360 718,-1373 C 726,-1377 735,-1380 743,-1383"
+         id="path1356" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="742.416,-1386.48 753,-1386 744.427,-1379.77 742.416,-1386.48 "
+         id="polygon1358" />
+    </g>
+<!-- tools->useredit -->    <g
+       id="edge230"
+       class="edge">
+      <title
+         id="title1361">tools->useredit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 619,-1326 C 636,-1353 673,-1402 718,-1423 C 801,-1461 912,-1454 974,-1446"
+         id="path1363" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="974.398,-1449.48 984,-1445 973.701,-1442.51 974.398,-1449.48 "
+         id="polygon1365" />
+    </g>
+<!-- user_queues -->    <g
+       id="node74"
+       class="node">
+      <title
+         id="title1368">user_queues</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="852,-1286 746,-1286 746,-1250 852,-1250 852,-1286 "
+         id="polygon1370" />
+      <text
+         x="799"
+         y="-1263.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1372">user_queues</text>
+    </g>
+<!-- tools->user_queues -->    <g
+       id="edge232"
+       class="edge">
+      <title
+         id="title1375">tools->user_queues</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 635,-1302 C 661,-1297 701,-1289 735,-1281"
+         id="path1377" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="735.881,-1284.39 745,-1279 734.508,-1277.53 735.881,-1284.39 "
+         id="polygon1379" />
+    </g>
+<!-- useradmin->logview -->    <g
+       id="edge234"
+       class="edge">
+      <title
+         id="title1382">useradmin->logview</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 845,-1397 C 959,-1394 1252,-1381 1322,-1319 C 1380,-1268 1403,-1176 1411,-1129"
+         id="path1384" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="1414.47,-1129.49 1413,-1119 1407.61,-1128.12 1414.47,-1129.49 "
+         id="polygon1386" />
+    </g>
+<!-- useradmin->useredit -->    <g
+       id="edge236"
+       class="edge">
+      <title
+         id="title1389">useradmin->useredit</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 845,-1406 C 882,-1413 936,-1422 974,-1429"
+         id="path1391" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="973.508,-1432.47 984,-1431 974.881,-1425.61 973.508,-1432.47 "
+         id="polygon1393" />
+    </g>
+<!-- user_queue -->    <g
+       id="node73"
+       class="node">
+      <title
+         id="title1396">user_queue</title>
+      <polygon
+         style="fill:none;stroke:#000000"
+         points="1070,-1286 972,-1286 972,-1250 1070,-1250 1070,-1286 "
+         id="polygon1398" />
+      <text
+         x="1021"
+         y="-1263.9"
+         style="font-size:14px;text-anchor:middle;font-family:Verdana"
+         id="text1400">user_queue</text>
+    </g>
+<!-- user_queues->user_queue -->    <g
+       id="edge238"
+       class="edge">
+      <title
+         id="title1403">user_queues->user_queue</title>
+      <path
+         style="fill:none;stroke:#000000;stroke-width:2"
+         d="M 853,-1268 C 886,-1268 927,-1268 961,-1268"
+         id="path1405" />
+      <polygon
+         style="fill:#000000;stroke:#000000"
+         points="961,-1271.5 971,-1268 961,-1264.5 961,-1271.5 "
+         id="polygon1407" />
+    </g>
+  </g>
+  <text
+     xml:space="preserve"
+     style="font-size:51.5411911px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;m [...]
+     x="1560.3209"
+     y="333.03387"
+     id="text3787"
+     sodipodi:linespacing="125%"><tspan
+       sodipodi:role="line"
+       id="tspan3789"
+       x="1560.3209"
+       y="333.03387">Page Module Structure</tspan><tspan
+       sodipodi:role="line"
+       x="1560.3209"
+       y="397.46036"
+       id="tspan3793">NetEpi Collection</tspan><tspan
+       sodipodi:role="line"
+       x="1560.3209"
+       y="461.88684"
+       id="tspan3795">October 2008</tspan><tspan
+       sodipodi:role="line"
+       x="1560.3209"
+       y="526.31335"
+       id="tspan3797" /><tspan
+       sodipodi:role="line"
+       x="1560.3209"
+       y="590.73987"
+       id="tspan3791" /></text>
+  <text
+     xml:space="preserve"
+     style="font-size:21.47549629px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none; [...]
+     x="1562.0092"
+     y="534.49304"
+     id="text3799"
+     sodipodi:linespacing="125%"><tspan
+       sodipodi:role="line"
+       id="tspan3801"
+       x="1562.0092"
+       y="534.49304">Copyright 2004-2010 Health Administration Corporation</tspan></text>
+</svg>
diff --git a/doc/DEVNOTES b/doc/DEVNOTES
new file mode 100644
index 0000000..c49096c
--- /dev/null
+++ b/doc/DEVNOTES
@@ -0,0 +1,25 @@
+update persons set last_update=(select max(event_timestamp) from user_log join cases using (case_id) where cases.person_id = persons.person_id and event_type like 'persons[%' group by person_id);
+
+svn cat -r 1581 casemgr/schema/schema.py > /tmp/x.py && (cd /tmp && PYTHONPATH=/usr/lib/cgi-bin/collection/ python -ic 'import x, sys, cocklebur.dbobj.table_describer, poobah;cocklebur.dbobj.table_describer.execute=poobah.e;s=x.define_db("::x:");s.get_table("persons").db.db_has_relation = lambda n: False;s.get_table("form_defs").create()')
+
+
+Diagnosing object leaks (app/app.py main loop):
+
+    import gc
+    print >> sys.stderr, 'collect', gc.collect()
+    print >> sys.stderr, 'objects', len(gc.get_objects())
+    #print >> sys.stderr, 'template cache', len(app._CachingTemplateLoaderMixin__cache)
+    #print >> sys.stderr, 'template cache', len(app._CachingTemplateLoaderMixin__cache)
+    #print >> sys.stderr, 'macros', len(app._ResourceMixin__macros)
+    #print >> sys.stderr, 'lookups', len(app._ResourceMixin__lookups)
+    #print >> sys.stderr, 'tags', len(app._ResourceMixin__tags)
+
+Checklist for adding new demographic fields:
+
+ * add column (and index) to casemgr/schema/schema.py
+ * add migration logic to tools/compile_db.py (pre_upgrade_schema)
+ * add field to casemgr/demogfields.py
+ * add field to casemgr/person.py, Person.person_attrs and to_query
+ * add field to casemgr/report/reportfilters.py
+ * add field to casemgr/cases.py CaseBase.new
+ * update tests (particularly tests/report.py and tests/export.py)
diff --git a/doc/HINTS b/doc/HINTS
new file mode 100644
index 0000000..8befddc
--- /dev/null
+++ b/doc/HINTS
@@ -0,0 +1,61 @@
+Find instances of form 'swineflu' where there is more than one instance
+associated with a case:
+
+    select cnt, case_id, surname, given_names, dob from 
+        (select count(*) as cnt, case_id 
+            from case_form_summary 
+            where form_label='swineflu' and not deleted
+            group by case_id) 
+        as formcnt 
+        join cases using (case_id)
+        join persons using (person_id)
+        where cnt > 1 and not deleted;
+
+Find instances duplicate singleton forms:
+
+    select * from 
+        (select count(*) as cnt, case_id 
+            from case_form_summary 
+            where form_label in 
+                (select label from forms where not allow_multiple)
+            group by form_label, case_id) 
+        as formcnt 
+        where cnt > 1;
+
+Report users who are actively using the system (since a given date):
+
+    select user_id, username, fullname, title, email, 
+            phone_home, phone_work, phone_mobile, phone_fax, 
+            agency, expertise 
+        from users 
+        where not deleted 
+            and enabled 
+            and user_id in 
+                (select user_id 
+                    from user_log 
+                    where event_timestamp >= '2009-5-1')
+        order by username;
+
+or (for CSV output):
+
+    psql -q -A -o activeusers -F, -c "select user_id, username, fullname, title, email, phone_home, phone_work, phone_mobile, phone_fax, agency, expertise from users where not deleted and enabled and user_id in (select user_id from user_log where event_timestamp >= '2009-5-1') order by username;" sftest
+
+Report persons who have multiple cases of syndrome_id 8:
+
+    select cnt, surname, given_names, dob 
+        from 
+            (select person_id, count(case_id) as cnt 
+                from cases 
+                where syndrome_id=8 and not deleted 
+                group by person_id) as foo 
+        join persons using (person_id) 
+        where cnt > 1;
+
+Copy data from form into core field:
+
+    update cases 
+        set case_assignment=sf.phu_for_case 
+        from case_form_summary, form_swineflu_00056 as sf 
+        where syndrome_id=8 
+            and case_form_summary.summary_id = sf.summary_id 
+            and cases.case_id = case_form_summary.case_id;
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..6c9016a
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+.PHONY: all
+all: Collection-ER.pdf Collection-ER.png
+
+# old dia: --export-to-format=eps-builtin
+#
+# Sigh, dia borked again
+#	dia --nosplash --filter=pdf \
+#		--export=$(subst .dia,.pdf,$<) $<
+%.pdf: %.dia
+	dia --nosplash --filter=svg \
+		--export=$(subst .dia,.svg,$<) $<
+	inkscape --without-gui --export-area-snap \
+		--export-pdf=$(subst .dia,.pdf,$<) \
+		$(subst .dia,.svg,$<)
+
+# old dia: --export-to-format=png
+%.png: %.dia
+	dia --nosplash --filter=png --size 1600 \
+		--export=$(subst .dia,.png,$<) $<
diff --git a/doc/sillyquery b/doc/sillyquery
new file mode 100644
index 0000000..f069c2c
--- /dev/null
+++ b/doc/sillyquery
@@ -0,0 +1,58 @@
+CREATE TEMPORARY VIEW swineflu_view
+    AS SELECT case_id, form_swineflu_00056.* FROM form_swineflu_00056
+        JOIN case_form_summary USING (summary_id)
+        WHERE NOT deleted;
+
+CREATE TEMPORARY VIEW case_fu_view
+    AS SELECT case_id, form_case_fu_00007.* FROM form_case_fu_00007
+        JOIN case_form_summary USING (summary_id)
+        WHERE NOT deleted;
+
+SELECT case_id, swineflu_view.summary_id, case_fu_view.summary_id
+    FROM cases 
+    JOIN persons USING (person_id) 
+    LEFT JOIN swineflu_view USING (case_id)
+    LEFT JOIN case_fu_view USING (case_id)
+    WHERE case_fu_view.summary_id IS NOT NULL OR (not cases.deleted 
+        AND syndrome_id = 8
+        AND swineflu_view.phu_for_case IN ('NSCCAHS_H','NSCCAHS_G')
+        AND NOT (swineflu_view.home_isolation_released IN ('True'))
+        AND cases.case_status IN ('suspected','probable','confirmed','susp_cannot_excl'))
+    ORDER BY surname asc,interpreter_req asc;
+
+SELECT case_id, swineflu_view.summary_id, case_fu_view.summary_id
+    FROM cases 
+    JOIN persons USING (person_id) 
+    LEFT JOIN (SELECT case_id, form_swineflu_00056.* FROM form_swineflu_00056
+            JOIN case_form_summary USING (summary_id)
+                    WHERE NOT deleted) 
+        AS swineflu_view USING (case_id)
+    LEFT JOIN (SELECT case_id, form_case_fu_00007.* FROM form_case_fu_00007
+            JOIN case_form_summary USING (summary_id)
+                    WHERE NOT deleted) 
+        AS case_fu_view USING (case_id)
+    WHERE case_fu_view.summary_id IS NOT NULL OR (not cases.deleted 
+        AND syndrome_id = 8
+        AND swineflu_view.phu_for_case IN ('NSCCAHS_H','NSCCAHS_G')
+        AND NOT (swineflu_view.home_isolation_released IN ('True'))
+        AND cases.case_status IN ('suspected','probable','confirmed','susp_cannot_excl'))
+    ORDER BY surname asc,interpreter_req asc;
+
+
+SELECT case_id, form_swineflu_00056.summary_id, form_case_fu_00007.summary_id
+    FROM cases 
+    JOIN persons USING (person_id) 
+    LEFT JOIN (SELECT case_id, form_swineflu_00056.* FROM form_swineflu_00056
+            JOIN case_form_summary USING (summary_id)
+                    WHERE NOT deleted) 
+        AS form_swineflu_00056 USING (case_id)
+    LEFT JOIN (SELECT case_id, form_case_fu_00007.* FROM form_case_fu_00007
+            JOIN case_form_summary USING (summary_id)
+                    WHERE NOT deleted) 
+        AS form_case_fu_00007 USING (case_id)
+    WHERE form_case_fu_00007.summary_id IS NOT NULL OR (not cases.deleted 
+        AND syndrome_id = 8
+        AND form_swineflu_00056.phu_for_case IN ('NSCCAHS_H','NSCCAHS_G')
+        AND NOT (form_swineflu_00056.home_isolation_released IN ('True'))
+        AND cases.case_status IN ('suspected','probable','confirmed','susp_cannot_excl'))
+    ORDER BY surname asc,interpreter_req asc;
diff --git a/forms/hospital_admit.form b/forms/hospital_admit.form
new file mode 100644
index 0000000..a92d875
--- /dev/null
+++ b/forms/hospital_admit.form
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<form name='hospital_admit' form_type='case' allow_multiple='True'>
+ <label>Hospital Admission (SARS)</label>
+ <question>
+  <label>Hospitalised</label>
+  <input name='admission_hospitalised' type='YesNo'>
+   <summary>hospitalised</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date hospitalised:</label>
+  <input name='admission_date' type='DateInput'>
+   <summary>admitted</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date discharged:</label>
+  <input name='admission_discharge_date' type='DateInput'>
+   <summary>discharged</summary>
+  </input>
+ </question>
+ <question>
+  <label>Hospital name:</label>
+  <input name='admission_hospital' type='TextInput' />
+ </question>
+ <question>
+  <label>Length of hospitalisation:</label>
+  <input name='admission_stay' type='IntInput'>
+   <post_text>days</post_text>
+  </input>
+ </question>
+ <question>
+  <label>Isolation</label>
+  <input name='admission_isolation' type='YesNo' />
+ </question>
+ <question>
+  <label>Mechanical Ventilation</label>
+  <input name='admission_mech_vent' type='YesNo' />
+ </question>
+ <question>
+  <label>Aerosol producing procedures</label>
+  <input name='admission_aerosol' type='YesNo' />
+  <input name='admission_aerosol_detail' type='TextInput'>
+   <pre_text>If yes, specify:</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>ICU admission</label>
+  <input name='admission_icu' type='YesNo' />
+  <input name='admission_icu_stay' type='IntInput'>
+   <pre_text>If yes, length of stay:</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Co-morbidities</label>
+  <input name='admission_co_morb' type='TextArea' />
+ </question>
+</form>
diff --git a/forms/lab_culture.form b/forms/lab_culture.form
new file mode 100644
index 0000000..cf9e7b0
--- /dev/null
+++ b/forms/lab_culture.form
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<form name='lab_culture' form_type='case' allow_multiple='True'>
+ <label>Lab test - Culture (SARS)</label>
+ <question>
+  <label>
+   At what stage of progression of the disease was the sample taken (More than
+   one series of results can be entered)
+  </label>
+  <input name='Respiratory_culture_stage' type='RadioList' required='True'>
+   <summary>Stage</summary>
+   <choices>
+    <choice name='Acute'>Acute</choice>
+    <choice name='Convalescent'>Convalescent</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   On what date was the sample taken? (More than one series of results can be
+   entered)
+  </label>
+  <input name='Respiratory_culture_date' type='DateInput' required='True'>
+   <summary>Respiratory sample date</summary>
+  </input>
+ </question>
+ <section>
+  <label>Respiratory Tract samples</label>
+  <question>
+   <label>Influenza A/B</label>
+   <input name='Influenza_culture' type='LabTestResult'>
+    <summary>Influenza A/B</summary>
+   </input>
+  </question>
+  <question>
+   <label>Adenovirus</label>
+   <input name='Adenovirus_culture' type='LabTestResult' required='True'>
+    <summary>Adenovirus</summary>
+   </input>
+  </question>
+  <question>
+   <label>Parainfluenza</label>
+   <input name='Parinfluenza_culture' type='LabTestResult' required='True'>
+    <summary>Parainfluenza</summary>
+   </input>
+  </question>
+  <question>
+   <label>Metapneumovirus</label>
+   <input name='Metapnem_culture' type='LabTestResult' required='True'>
+    <summary>Metapneumovirus</summary>
+   </input>
+  </question>
+  <question>
+   <label>Bacterial (respiratory tract)</label>
+   <input name='Bacterial_culture' type='LabTestResult' required='True'>
+    <summary>Bacterial (tract)</summary>
+   </input>
+  </question>
+  <question>
+   <label>Fungal</label>
+   <input name='Fungal_culture' type='LabTestResult' required='True'>
+    <summary>Fungal</summary>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Serum samples</label>
+  <question>
+   <label>Bacterial (serum)</label>
+   <input name='Bacterial_serum_culture' type='LabTestResult' required='True'>
+    <summary>Bacterial (serum)</summary>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/lab_direct_ag.form b/forms/lab_direct_ag.form
new file mode 100644
index 0000000..9309f05
--- /dev/null
+++ b/forms/lab_direct_ag.form
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<form name='lab_direct_ag' form_type='case' allow_multiple='True'>
+ <label>Lab test - Direct Antigen (SARS)</label>
+ <question>
+  <label>
+   At what stage of progression of the disease was the sample taken (More than
+   one series of results can be entered)
+  </label>
+  <input name='Direct_antigen_stage' type='RadioList'>
+   <choices>
+    <choice name='Acute'>Acute</choice>
+    <choice name='Convalescent'>Convalescent</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   On what date was the sample taken? (More than one series of results can be
+   entered)
+  </label>
+  <input name='Direct_antigen_date' type='DateInput'>
+   <pre_text>Date collected</pre_text>
+  </input>
+ </question>
+ <section>
+  <label>Respiratory tract</label>
+  <question>
+   <label>Influenza A/B</label>
+   <input name='Influenza_DAntigen' type='LabTestResult' required='True'>
+    <summary>Influenza</summary>
+   </input>
+  </question>
+  <question>
+   <label>RSV</label>
+   <input name='RSV_DAntigen' type='LabTestResult' required='True'>
+    <summary>RSV</summary>
+   </input>
+  </question>
+  <question>
+   <label>Adenovirus</label>
+   <input name='Adenovirus_DAntigen' type='LabTestResult' required='True'>
+    <summary>Adenovirus</summary>
+   </input>
+  </question>
+  <question>
+   <label>Parainfluenza</label>
+   <input name='Parainfluenza_DAntigen' type='LabTestResult' required='True'>
+    <summary>Parainfluenza</summary>
+   </input>
+  </question>
+  <question>
+   <label>Metapneumovirus</label>
+   <input name='Metapn_DAntigen' type='LabTestResult' required='True'>
+    <summary>Metapneumovirus</summary>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/lab_haem_biochem.form b/forms/lab_haem_biochem.form
new file mode 100644
index 0000000..d6979e4
--- /dev/null
+++ b/forms/lab_haem_biochem.form
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<form name='lab_haem_biochem' form_type='case' allow_multiple='True'>
+ <label>Lab test - Haematology and biochemistry (SARS)</label>
+ <question>
+  <label>
+   At what stage of progression of the disease was the sample taken (More than
+   one series of results can be entered)
+  </label>
+  <input name='Biochemistry_stage' type='RadioList' required='True'>
+   <summary>Stage</summary>
+   <choices>
+    <choice name='Acute'>Acute</choice>
+    <choice name='Convalescent'>Convalescent</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   On what date was the sample taken? (More than one series of results can be
+   entered)
+  </label>
+  <input name='Biochemistry_date' type='DateInput'>
+   <summary>Date</summary>
+   <pre_text>Date of collection</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>White cell count</label>
+  <input name='WCC' type='FloatInput'>
+   <summary><![CDATA[WCC (x10<sup>9</sup>/L)]]></summary>
+   <pre_text>White cell count</pre_text>
+   <post_text><![CDATA[x10<sup>9</sup>/L]]></post_text>
+  </input>
+ </question>
+ <question>
+  <label>Lymphocytes</label>
+  <input name='Lymphocytes' type='FloatInput'>
+   <summary><![CDATA[Lymphocytes (x10<sup>9</sup>/L)]]></summary>
+   <pre_text>Lymphocyte count</pre_text>
+   <post_text><![CDATA[x10<sup>9</sup>/L]]></post_text>
+  </input>
+ </question>
+ <question>
+  <label>Neutrophils</label>
+  <input name='Neutrophils' type='FloatInput'>
+   <summary><![CDATA[Neutrophils (x10<sup>9</sup>/L)]]></summary>
+   <pre_text>Neutrophil count</pre_text>
+   <post_text><![CDATA[x10<sup>9</sup>/L]]></post_text>
+  </input>
+ </question>
+ <question>
+  <label>Platelets</label>
+  <input name='Platelet' type='FloatInput'>
+   <summary><![CDATA[Platelets (x10<sup>9</sup>/L)]]></summary>
+   <pre_text>Platelet count</pre_text>
+   <post_text><![CDATA[x10<sup>9</sup>/L]]></post_text>
+  </input>
+ </question>
+ <question>
+  <label>ALT</label>
+  <input name='ALT' type='FloatInput'>
+   <summary>ALT (U/L)</summary>
+   <pre_text>ALT</pre_text>
+   <post_text>(U/L)</post_text>
+  </input>
+ </question>
+ <question>
+  <label>AST</label>
+  <input name='AST' type='FloatInput'>
+   <summary>AST (U/L)</summary>
+   <pre_text>AST</pre_text>
+   <post_text>U/L</post_text>
+  </input>
+ </question>
+ <question>
+  <label>LDH</label>
+  <input name='LDH' type='FloatInput'>
+   <summary>LDH (U/L)</summary>
+   <pre_text>LDH</pre_text>
+   <post_text>U/L</post_text>
+  </input>
+ </question>
+ <question>
+  <label>CK</label>
+  <input name='CK' type='FloatInput'>
+   <summary>CK (U/L)</summary>
+   <pre_text>CK</pre_text>
+   <post_text>U/L</post_text>
+  </input>
+ </question>
+</form>
diff --git a/forms/lab_pcr.form b/forms/lab_pcr.form
new file mode 100644
index 0000000..c26a82b
--- /dev/null
+++ b/forms/lab_pcr.form
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<form name='lab_pcr' form_type='case' allow_multiple='True'>
+ <label>Lab test - PCR (SARS)</label>
+ <question>
+  <label>
+   At what stage of progression of the disease was the sample taken (More than
+   one series of results can be entered)
+  </label>
+  <input name='PCR_stage' type='RadioList' required='True'>
+   <summary>Stage</summary>
+   <choices>
+    <choice name='Acute'>Acute</choice>
+    <choice name='Convalescent'>Convalescent</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   On what date was the sample taken? (More than one series of results can be
+   entered)
+  </label>
+  <input name='PCR_date' type='DateInput'>
+   <summary>Date</summary>
+   <pre_text>Date of collection</pre_text>
+  </input>
+ </question>
+ <section>
+  <label>Blood</label>
+  <question>
+   <label>SARS coronavirus</label>
+   <input name='SARS_serum_PCR' type='LabTestResult' required='True'>
+    <summary>SARS coronavirus</summary>
+   </input>
+  </question>
+  <question>
+   <label>Tuberculosis</label>
+   <input name='TB_serum_PCR' type='LabTestResult' required='True'>
+    <summary>TB</summary>
+   </input>
+  </question>
+  <question>
+   <label>Enteroviruses</label>
+   <input name='Enterovirus_serum_PCR' type='LabTestResult' required='True'>
+    <summary>Enteroviruses</summary>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Respiratory tract</label>
+  <question>
+   <label>Chlamydia pneumoniae</label>
+   <input name='Chlamydia_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Chlamydia pn.</summary>
+   </input>
+  </question>
+  <question>
+   <label>Mycoplasma pneumoniae</label>
+   <input name='Mycoplasma_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Mycoplasma pneumoniae</summary>
+   </input>
+  </question>
+  <question>
+   <label>Influenza A/B</label>
+   <input name='Flu_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Influenza A/B</summary>
+   </input>
+  </question>
+  <question>
+   <label>RSV</label>
+   <input name='RSV_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>RSV</summary>
+   </input>
+  </question>
+  <question>
+   <label>Adenovirus</label>
+   <input name='Adenovirus_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Adenovirus</summary>
+   </input>
+  </question>
+  <question>
+   <label>Parainfluenza</label>
+   <input name='Parainfluenza_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Parainfluenza</summary>
+   </input>
+  </question>
+  <question>
+   <label>Metapneumovirus</label>
+   <input name='Metapn_nasophar_PCR' type='LabTestResult' required='True'>
+    <summary>Metapneumovirus</summary>
+   </input>
+  </question>
+  <section>
+   <label></label>
+  </section>
+  <question>
+   <label>Mycoplasma pneumoniae</label>
+   <input name='Mycoplasma_sputum_PCR' type='LabTestResult'>
+    <summary>Mycoplasma</summary>
+   </input>
+  </question>
+  <question>
+   <label>Chlamydia pneumoniae</label>
+   <input name='Chlamydiapn_sputum_PCR' type='LabTestResult'>
+    <summary>Chlamydia pneumoniae</summary>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Faeces</label>
+  <question>
+   <label>SARS coronavirus</label>
+   <input name='SARS_faecal_PCR' type='LabTestResult'>
+    <summary>SARS coronavirus</summary>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/lab_serology.form b/forms/lab_serology.form
new file mode 100644
index 0000000..649543b
--- /dev/null
+++ b/forms/lab_serology.form
@@ -0,0 +1,148 @@
+<?xml version="1.0"?>
+<form name='lab_serology' form_type='case' allow_multiple='True'>
+ <label>Lab test - Serology (SARS)</label>
+ <question>
+  <label>
+   At what stage of progression of the disease was the sample taken (More than
+   one series of results can be entered)
+  </label>
+  <input name='Serology_stage' type='RadioList' required='True'>
+   <summary>Stage</summary>
+   <choices>
+    <choice name='Acute'>Acute</choice>
+    <choice name='Convalescent'>Convalescent</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   At what date was the sample taken? (More than one series of results can be
+   entered)
+  </label>
+  <input name='Serology_date' type='DateInput' required='True'>
+   <summary>Date of sample</summary>
+  </input>
+ </question>
+ <question>
+  <label>SARS coronavirus</label>
+  <input name='SARS_coronavirus' type='RadioList' required='True'>
+   <summary>SARS coronavirus</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Legionella spp.</label>
+  <input name='Legionella' type='RadioList' required='True'>
+   <summary>Legionella</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Chlamydia psittaci</label>
+  <input name='chlamydia_ps' type='RadioList' required='True'>
+   <summary>Chalmydia</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Chlamydia pneumoniae</label>
+  <input name='Chlamydia_pn' type='LabTestResult' />
+ </question>
+ <question>
+  <label>Influenza A/B</label>
+  <input name='Influenza' type='RadioList' required='True'>
+   <summary>Influenza</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Q fever</label>
+  <input name='Q_fever' type='RadioList' required='True'>
+   <summary>Q fever</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Adenovirus</label>
+  <input name='Adenovirus' type='RadioList' required='True'>
+   <summary>Adenovirus</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Mycoplasma</label>
+  <input name='Mycoplasma' type='RadioList' required='True'>
+   <summary>Mycoplasma</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>RSV</label>
+  <input name='RSV' type='RadioList' required='True'>
+   <summary>RSV</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Enteroviruses</label>
+  <input name='Enterovirus' type='RadioList' required='True'>
+   <summary>Enterovirus</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Hendra</label>
+  <input name='Hendra' type='RadioList' required='True'>
+   <summary>Hendra</summary>
+   <choices>
+    <choice name='Positive'>Positive</choice>
+    <choice name='Negative'>Negative</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Other (please specify)</label>
+  <input name='Other_serology' type='TextInput'>
+   <summary>Other</summary>
+   <pre_text>Other disease test</pre_text>
+  </input>
+ </question>
+</form>
diff --git a/forms/sars_china.form b/forms/sars_china.form
new file mode 100644
index 0000000..5622db9
--- /dev/null
+++ b/forms/sars_china.form
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+<form name='sars_china' form_type='case' allow_multiple='True'>
+ <label>Travel history - China (SARS)</label>
+ <section>
+  <label>Transit in China</label>
+  <question>
+   <label>Transited through China (as distinct from travelled in China)</label>
+   <input name='Transit_airport' type='TextInput'>
+    <summary>Transit airport</summary>
+    <pre_text>Airport</pre_text>
+   </input>
+   <input name='Transit_date' type='DateInput'>
+    <summary>Transit date</summary>
+    <pre_text>Transit date</pre_text>
+   </input>
+   <input name='Transit_period' type='IntInput'>
+    <summary>Transit period</summary>
+    <pre_text>Transit period</pre_text>
+    <post_text>whole hours</post_text>
+   </input>
+  </question>
+  <question>
+   <label>While in transit, did you leave the airport?</label>
+   <input name='Left_airport' type='YesNo'>
+    <summary>Left airport</summary>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Travel in China</label>
+  <question>
+   <label>Total period spent in China</label>
+   <input name='China_arrival' type='DateInput' required='True'>
+    <summary>Arrived China:</summary>
+    <pre_text>Arrived in China</pre_text>
+   </input>
+   <input name='China_depart' type='DateInput' required='True'>
+    <summary>Departed China:</summary>
+    <pre_text>Departed China</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Did you visit Guangdong?</label>
+   <input name='Guangdong' type='YesNo' required='True'>
+    <summary>Visited Guangdong</summary>
+   </input>
+   <input name='Guangdong_arrival' type='DateInput'>
+    <pre_text>Arrived in Guangdong</pre_text>
+   </input>
+   <input name='Guangdong_depart' type='DateInput'>
+    <pre_text>DepartedGuangdong</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Did you visit Shanxi?</label>
+   <input name='Shanxi' type='YesNo' required='True'>
+    <summary>Visited Shanxi</summary>
+   </input>
+   <input name='Shanxi_arrival' type='DateInput'>
+    <pre_text>Arrived in Shanxi</pre_text>
+   </input>
+   <input name='Shanxi_deprt' type='DateInput'>
+    <pre_text>Departed Shanxi</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Did you visit Beijing?</label>
+   <input name='Beijing' type='YesNo' required='True'>
+    <summary>Visited Beijing</summary>
+   </input>
+   <input name='Beijing_arrival' type='DateInput'>
+    <pre_text>Arrived in Beijing</pre_text>
+   </input>
+   <input name='Beijing_depart' type='DateInput'>
+    <pre_text>Departed Beijing</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Did you visit any other provinces?
+Please specify</label>
+   <input name='Province1' type='TextInput'>
+    <summary>Other provinces:</summary>
+    <pre_text>Province name</pre_text>
+   </input>
+   <input name='Prov1_arrival' type='DateInput'>
+    <pre_text>Arrived</pre_text>
+   </input>
+   <input name='Prov1_depart' type='DateInput'>
+    <pre_text>departed</pre_text>
+   </input>
+  </question>
+  <question>
+   <label></label>
+   <input name='Province2' type='TextInput'>
+    <pre_text>Province name</pre_text>
+   </input>
+   <input name='Prov2_arrival' type='DateInput'>
+    <pre_text>Arrived</pre_text>
+   </input>
+   <input name='Prov2_depart' type='DateInput'>
+    <pre_text>Departed</pre_text>
+   </input>
+  </question>
+  <question>
+   <label></label>
+   <input name='Province3' type='TextInput'>
+    <pre_text>Province name</pre_text>
+   </input>
+   <input name='Prov3_arrival' type='DateInput'>
+    <pre_text>Arrived</pre_text>
+   </input>
+   <input name='Prov3_depart' type='DateInput'>
+    <pre_text>Departed</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>While travelling did you visit a health care facility?</label>
+   <input name='China_hcfac' type='YesNo' required='True'>
+    <summary>Visited health care fac:</summary>
+   </input>
+   <input name='Hcfac_date' type='DateInput'>
+    <pre_text>Date of visit</pre_text>
+   </input>
+   <input name='Hcfac_name' type='TextInput'>
+    <pre_text>Name of facility, town, and province</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Reason for visit</label>
+   <input name='Reason_travel' type='CheckBoxes'>
+    <summary>Reason</summary>
+    <choices>
+     <choice name='Business'>Business</choice>
+     <choice name='Holiday'>Holiday</choice>
+     <choice name='Visit'>Visit Family/friends</choice>
+     <choice name='Other'>Other (please specify)</choice>
+    </choices>
+   </input>
+   <input name='Reason_other' type='TextInput'>
+    <pre_text>Please specify</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>If you were on holiday, were you part of a tour?</label>
+   <input name='Tour' type='YesNo' />
+   <input name='Tour_org' type='TextInput'>
+    <pre_text>Tour name and tour company</pre_text>
+   </input>
+   <input name='Tour_other' type='TextInput'>
+    <pre_text>Other countries visited on tour</pre_text>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/sars_exposure.form b/forms/sars_exposure.form
new file mode 100644
index 0000000..77f7d6a
--- /dev/null
+++ b/forms/sars_exposure.form
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<form name='sars_exposure' form_type='case' allow_multiple='False'>
+ <label>Exposure History (SARS)</label>
+ <question>
+  <label>
+   Close contact with person diagnosed with SARS in 10 days prior to onset.
+   [Close contact is defined as....]
+  </label>
+  <input name='Close_contact' type='RadioList' required='True'>
+   <summary>Contact with case</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Duration of contact (in hours)</label>
+  <input name='Contact_duration' type='FloatInput'>
+   <summary>Contact duration (hours)</summary>
+   <post_text>hours</post_text>
+  </input>
+ </question>
+ <question>
+  <label>Date of first contact with person with SARS</label>
+  <input name='Contact_date_first' type='DateInput'>
+   <summary>Date of first contact</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date of last contact with a person with SARS</label>
+  <input name='Contact_date_last' type='DateInput'>
+   <summary>Most recent contact</summary>
+  </input>
+ </question>
+</form>
diff --git a/forms/sars_followup.form b/forms/sars_followup.form
new file mode 100644
index 0000000..24479f4
--- /dev/null
+++ b/forms/sars_followup.form
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<form name='sars_followup' form_type='case' allow_multiple='True'>
+ <label>SARS Contact Follow-up</label>
+ <question>
+  <label>Follow-up date</label>
+  <input name='form_date' type='DatetimeInput' />
+ </question>
+ <question>
+  <label>
+   <![CDATA[Temperature at first taking?<br><font size="-1">If 38 degrees or
+   over arrange urgent protected medical assessment, if between 37.5 and 38
+   degrees celsius discuss with clinician</font>]]>
+  </label>
+  <input name='first_temperature' type='BodyTemperatureInput'>
+   <summary>1st Temp</summary>
+  </input>
+ </question>
+ <question>
+  <label>
+   <![CDATA[Temperature at second taking?<br><font size="-1">If 38 degrees or
+   over arrange urgent protected medical assessment, if between 37.5 and 38
+   degrees celsius discuss with clinician.</font>]]>
+  </label>
+  <input name='second_temperature' type='BodyTemperatureInput'>
+   <summary>2st Temp</summary>
+  </input>
+ </question>
+ <question>
+  <label>
+   <![CDATA[Has the contact taken any medications that could reduce
+   temperature (e.g. paracetamol, aspirin, other pain relievers)?<br><font
+   size="-1">If yes, arrange a further telephone call to check the person's
+   temperature four hours after medication taken.</font>]]>
+  </label>
+  <input name='antipyretic_medication' type='YesNo' />
+ </question>
+ <section>
+  <label>Does the contact have any of these symptoms</label>
+  <question>
+   <label>Feels generally unwell (malaise)?</label>
+   <input name='malaise' type='YesNo'>
+    <summary>malaise</summary>
+   </input>
+  </question>
+  <question>
+   <label>Chills?</label>
+   <input name='chills' type='YesNo'>
+    <summary>chills</summary>
+   </input>
+  </question>
+  <question>
+   <label>Rigours?</label>
+   <input name='rigors' type='YesNo'>
+    <summary>rigours</summary>
+   </input>
+  </question>
+  <question>
+   <label>Headache?</label>
+   <input name='headache' type='YesNo'>
+    <summary>headache</summary>
+   </input>
+  </question>
+  <question>
+   <label>Cough?</label>
+   <input name='cough' type='YesNo'>
+    <summary>cough</summary>
+   </input>
+  </question>
+  <question>
+   <label>Diarrhoea?</label>
+   <input name='diarrhoea' type='YesNo'>
+    <summary>diarrhoea</summary>
+   </input>
+  </question>
+  <question>
+   <label>If yes, are own toilet facilities available?</label>
+   <input name='toilet_isolation' type='YesNo' />
+  </question>
+  <question>
+   <label>Shortness of breath or difficulty breathing?</label>
+   <input name='breathing_difficulty' type='YesNo'>
+    <summary>difficulty breathing</summary>
+   </input>
+  </question>
+ </section>
+ <question>
+  <label>
+   Are any other houshold members showing any symptoms listed above? If yes,
+   arrange protected medical assessment.
+  </label>
+  <input name='other_household_symptoms' type='YesNo' />
+ </question>
+ <question>
+  <label>
+   Have you been away from home in the last 24 hours? If yes, provide
+   information sheets on the importance of quarantine.
+  </label>
+  <input name='away_from_home' type='YesNo' />
+ </question>
+ <question>
+  <label>
+   Dou you have any immediate or impending compliance issues (e.g. need to go
+   out for groceries, running out of masks, child care, etc)?
+  </label>
+  <input name='compliance_issues' type='YesNo' />
+ </question>
+ <question>
+  <label>Other comments</label>
+  <input name='other_comments' type='TextArea' />
+ </question>
+</form>
diff --git a/forms/sars_hk.form b/forms/sars_hk.form
new file mode 100644
index 0000000..b8320f2
--- /dev/null
+++ b/forms/sars_hk.form
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<form name='sars_hk' form_type='case' allow_multiple='True'>
+ <label>Travel history - Hong Kong (SARS)</label>
+ <section>
+  <label>Transit in Hong Kong</label>
+  <question>
+   <label>
+    Transited through Hong Kong(as distinct from travelled in Hong Kong)
+   </label>
+   <input name='HK_transit_airport' type='TextInput'>
+    <summary>Transit airport</summary>
+    <pre_text>Airport</pre_text>
+   </input>
+   <input name='HK_transit_date' type='DateInput'>
+    <summary>Transit date</summary>
+    <pre_text>Transit date</pre_text>
+   </input>
+   <input name='HK_transit_period' type='IntInput'>
+    <pre_text>Transit period</pre_text>
+    <post_text>Whole hours</post_text>
+   </input>
+  </question>
+  <question>
+   <label>While in transit, did you leave the airport?</label>
+   <input name='HK_transit_left' type='YesNo' />
+  </question>
+  <section>
+   <label>Travel in China</label>
+   <question>
+    <label>Total period spent in Hong Kong</label>
+    <input name='HK_arrival' type='DateInput'>
+     <summary>Arrived</summary>
+     <pre_text>Arrived in HK</pre_text>
+    </input>
+    <input name='HK_depart' type='DateInput'>
+     <pre_text>Departed HK</pre_text>
+    </input>
+   </question>
+   <question>
+    <label>Did you stay in or visit the Metropole Hotel?</label>
+    <input name='Metropole' type='YesNo' />
+    <input name='Metro_arrival' type='DateInput'>
+     <pre_text>Arrival date</pre_text>
+    </input>
+    <input name='Metro_depart' type='DateInput'>
+     <pre_text>Departure date</pre_text>
+    </input>
+   </question>
+   <question>
+    <label>Did you stay in or visit the Amoy Gardens?</label>
+    <input name='Amoy' type='YesNo' />
+    <input name='Amoy_arrival' type='DateInput'>
+     <pre_text>Arrival date</pre_text>
+    </input>
+    <input name='Amoy_depart' type='DateInput'>
+     <pre_text>Departure date</pre_text>
+    </input>
+   </question>
+   <question>
+    <label>
+     Did you stay in any other hotels or apartment buildings? (Please specify)
+    </label>
+    <input name='Hotel1' type='TextInput'>
+     <pre_text>Hotel name</pre_text>
+    </input>
+    <input name='Hotel1_arrival' type='DateInput'>
+     <pre_text>Arrival date</pre_text>
+    </input>
+    <input name='Hotel1_depart' type='DateInput'>
+     <pre_text>Departure date</pre_text>
+    </input>
+   </question>
+  </section>
+ </section>
+</form>
diff --git a/forms/sars_onset.form b/forms/sars_onset.form
new file mode 100644
index 0000000..ac423b4
--- /dev/null
+++ b/forms/sars_onset.form
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<form name='sars_onset' form_type='case' allow_multiple='False'>
+ <label>Onset symptoms</label>
+ <question>
+  <label>Highest temperature since onset of symptoms:</label>
+  <input name='symptoms_HighestTemp' type='BodyTemperatureInput'>
+   <summary>highest temp</summary>
+  </input>
+ </question>
+ <question>
+  <label>Does the patient have a cough?</label>
+  <input name='symptoms_Cough' type='RadioList'>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Tubed'>Ventilated/paralysed</choice>
+    <choice name='Unknown'>Unknown</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Shortness of breath/difficulty breathing?</label>
+  <input name='symptoms_SOB' type='YesNo'>
+   <summary>difficulty breathing</summary>
+  </input>
+ </question>
+ <question>
+  <label>Signs of pneumonia?</label>
+  <input name='symptoms_Pneumonia' type='RadioList'>
+   <choices>
+    <choice name='Clinical'>Clinical</choice>
+    <choice name='Radiographic'>Radiographic</choice>
+    <choice name='Both'>Clinical and radiographic</choice>
+    <choice name='Unknown'>Unknown</choice>
+    <choice name='None'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Adult Respiratory Distress Syndrome (ARDS)?</label>
+  <input name='symptoms_ARDS' type='YesNo' />
+ </question>
+ <question>
+  <label>Other symptoms or relevant findings:</label>
+  <input name='symptoms_Other' type='TextArea' />
+ </question>
+</form>
diff --git a/forms/sars_symptoms.form b/forms/sars_symptoms.form
new file mode 100644
index 0000000..9347900
--- /dev/null
+++ b/forms/sars_symptoms.form
@@ -0,0 +1,184 @@
+<?xml version="1.0"?>
+<form name='sars_symptoms' form_type='case' allow_multiple='False'>
+ <label>Onset symptoms (SARS)</label>
+ <question>
+  <label><![CDATA[Fever >38 degrees C]]></label>
+  <input name='Fever' type='RadioList' required='True'>
+   <summary><![CDATA[Fever >38C]]></summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Date of fever onset</label>
+  <input name='Fever_onset' type='DateInput' required='True'>
+   <summary>Fever onset date</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date of onset of first symptom</label>
+  <input name='Onset_first_symptom' type='DateInput' required='True' />
+ </question>
+ <question>
+  <label>Productive cough</label>
+  <input name='Productive_cough' type='RadioList' required='True'>
+   <summary>Productive cough</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Non-Productive Cough</label>
+  <input name='Nonproductive_cough' type='RadioList'>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Difficulty breathing</label>
+  <input name='Diff_breathing' type='RadioList' required='True'>
+   <summary>Difficulty Breathing</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Headache</label>
+  <input name='Headache' type='RadioList'>
+   <summary>Headache</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Myalgia</label>
+  <input name='Myalgia' type='RadioList'>
+   <summary>Myalgia</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Diarrhoea</label>
+  <input name='Diarrhoea' type='RadioList' required='True'>
+   <summary>Diarrhoea</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Sore throat</label>
+  <input name='Sore_throat' type='RadioList'>
+   <summary>Sore throat</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Rigors</label>
+  <input name='Rigors' type='RadioList'>
+   <summary>Rigors</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Nausea/vomiting</label>
+  <input name='Nausea_vomiting' type='RadioList' required='True'>
+   <summary>Nausea/vomiting</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Dizziness</label>
+  <input name='Dizziness' type='RadioList'>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Was an x-ray taken?</label>
+  <input name='xray_done' type='RadioList'>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>X-ray date</label>
+  <input name='xray_date' type='DateInput' />
+ </question>
+ <question>
+  <label>X-ray findings of pneumonia</label>
+  <input name='xray_pneumonia' type='RadioList'>
+   <summary>X-ray findings of pneumonia</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Description of x_ray findings</label>
+  <input name='xray_description' type='RadioList'>
+   <summary>x-ray description</summary>
+   <choices>
+    <choice name='xray1'>Lobar</choice>
+    <choice name='xray2'>Atypical</choice>
+    <choice name='xray3'>Not applicable</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>
+   Autopsy pathology of Respiratory Distress Syndrome without identifiable
+   cause
+  </label>
+  <input name='autopsy_rds' type='RadioList' required='True'>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+    <choice name='None'>Not applicable</choice>
+    <choice name='Unanswered'>Not answered</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/forms/sars_travel.form b/forms/sars_travel.form
new file mode 100644
index 0000000..05940c7
--- /dev/null
+++ b/forms/sars_travel.form
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<form name='sars_travel' form_type='case' allow_multiple='True'>
+ <label>Travel history (SARS)</label>
+ <question>
+  <label>
+   <![CDATA[<b>History of travel to or transit through an area affected by
+   SARS</b> <br> <br>Please list all countries visited,along with the relevant
+   dates of arrival and departure, and flight numbers.]]>
+  </label>
+ </question>
+ <question>
+  <label>
+   For people visiting China, Hong Kong, Vietnam, Canada, and Singapore please
+   complete individual country travel forms as well.
+  </label>
+ </question>
+ <question>
+  <label>Country</label>
+  <input name='Country' type='TextInput' required='True'>
+   <summary>Country</summary>
+   <pre_text>Country</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Date of arrival</label>
+  <input name='Arrival_date' type='DateInput' required='True'>
+   <summary>Arrival date</summary>
+   <pre_text>Arrival date</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Arrival flight number</label>
+  <input name='Arrival_flight' type='TextInput'>
+   <pre_text>Arrival flight number</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Date of departure</label>
+  <input name='Departure_date' type='DateInput'>
+   <pre_text>Departure date</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Departure flight number</label>
+  <input name='Departure_flight' type='TextInput'>
+   <pre_text>Departure flight number</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Duration of visit</label>
+  <input name='Duration' type='IntInput'>
+   <summary>Duration of visit (days)</summary>
+   <pre_text>Duration</pre_text>
+   <post_text>Whole days</post_text>
+  </input>
+ </question>
+</form>
diff --git a/forms/spox_case.form b/forms/spox_case.form
new file mode 100644
index 0000000..712de25
--- /dev/null
+++ b/forms/spox_case.form
@@ -0,0 +1,220 @@
+<?xml version="1.0"?>
+<form name='spox_case' form_type='case' allow_multiple='False'>
+ <label>Case details (smallpox)</label>
+ <question>
+  <label>
+   Has the patient had a fever in the four days prior to the onset of the
+   rash, as part of this illness?
+  </label>
+  <input name='Fever_prior' type='YesNo' />
+  <input name='Fever_prior_onset' type='DateInput'>
+   <pre_text>Date of onset of fever prior to rash</pre_text>
+  </input>
+  <input name='Fever_prior_temp' type='FloatInput'>
+   <summary>Fever prior to rash</summary>
+   <pre_text>Fever temperature (max.)</pre_text>
+   <post_text><![CDATA[<sup>o</sup>C]]></post_text>
+  </input>
+  <input name='Fever_prior_max' type='DateInput'>
+   <pre_text>Date of max. fever temp., prior to rash</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Date of rash onset</label>
+  <input name='Rash_date' type='DateInput'>
+   <summary>Date of rash onset</summary>
+  </input>
+ </question>
+ <question>
+  <label>Is the rash accompanied by a cough?</label>
+  <input name='Cough' type='YesNo' />
+  <input name='Cough_date' type='DateInput'>
+   <pre_text>Date of onset of cough</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Symptoms experienced during the four days preceding rash onset</label>
+  <input name='other_symptom' type='CheckBoxes'>
+   <summary>Other symptoms, prior to rash</summary>
+   <choices>
+    <choice name='Head'>Headache</choice>
+    <choice name='Back'>Backache</choice>
+    <choice name='Chills'>Chills</choice>
+    <choice name='Vomit'>Vomiting</choice>
+    <choice name='Other'>Other, eg abdominal pain, delirium</choice>
+   </choices>
+  </input>
+  <input name='Other_detail' type='TextInput'>
+   <pre_text>If other, please specify</pre_text>
+   <post_text>None</post_text>
+  </input>
+ </question>
+ <question>
+  <label>How are the lesions distributed?</label>
+  <input name='Lesion_distn' type='RadioList'>
+   <choices>
+    <choice name='Centrifugal'>
+     Generalised, predominantly face and distal extremities (centrifugal)
+    </choice>
+    <choice name='Cetripetal'>
+     Generalised, predominantly trunk (centripetal)
+    </choice>
+    <choice name='Localized'>Localised, not generalised</choice>
+    <choice name='Other' />
+   </choices>
+  </input>
+  <input name='Lesion_detail' type='TextInput'>
+   <pre_text>If other, please specify</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Clinical type of smallpox</label>
+  <input name='Clinical_type' type='RadioList'>
+   <summary>Clinical type</summary>
+   <choices>
+    <choice name='Ordinary'>
+     Ordinary/classic type (raised, pustular lesions)
+    </choice>
+    <choice name='Sine'>Variola sine eruptione (Fever without rash)</choice>
+    <choice name='Modified'>
+     Modified type (As for ordinary but with an accelarated, less severe
+     course)
+    </choice>
+    <choice name='Flat'>
+     Flat type (Pustules remain flat, usually confluent or semi-confluent)
+    </choice>
+    <choice name='Haem'>
+     Haemorrhagic (widespread haemorrhages in skin and mucous membranes)
+    </choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Sub types for ordinary/classic cases</label>
+  <input name='ordinary_subtype' type='RadioList'>
+   <choices>
+    <choice name='Discrete'>
+     Discrete lesions (areas of normal skin between lesions, even on face)
+    </choice>
+    <choice name='Semi'>
+     Semi-confluent (Confluent rash on face, discrete elsewhere)
+    </choice>
+    <choice name='Confluent'>
+     Confluent (Confluent rash on face and forearms)
+    </choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Subtypes for haemorrhagic type</label>
+  <input name='haem_subtypes' type='RadioList'>
+   <choices>
+    <choice name='Early'>Early (with purpuric rash)</choice>
+    <choice name='Late'>Late (with haemorrhage into base pustules)</choice>
+   </choices>
+  </input>
+ </question>
+ <section>
+  <label>Clinical course</label>
+  <question>
+   <label>Did the patient develop any complications?</label>
+   <input name='complications' type='YesNo'>
+    <summary>Complications</summary>
+   </input>
+   <input name='complic_details' type='CheckBoxes'>
+    <summary>Details</summary>
+    <choices>
+     <choice name='Skin'>Skin, infected lesions/abscesses</choice>
+     <choice name='Cornea'>Corneal ulcer or keratitis</choice>
+     <choice name='Encephalitis'>Encephalitis</choice>
+     <choice name='Arthritis'>Arthritis</choice>
+     <choice name='Pneumonia'>Pneumonia</choice>
+     <choice name='Haemorrhagic'>Haemorrhagic</choice>
+     <choice name='Shock'>Shock</choice>
+     <choice name='Sepsis'>Bacterial sepsis</choice>
+     <choice name='Other'>Other</choice>
+    </choices>
+   </input>
+   <input name='Complication_other' type='TextInput'>
+    <pre_text>If other, please specify</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Antiviral medication (cidofovir)</label>
+   <input name='Cidofovir' type='YesNo' />
+   <input name='Cidofovir_date' type='DateInput'>
+    <pre_text>Date Cidofovir started</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Other antiviral medications given?</label>
+   <input name='Other_antiviral' type='YesNo' />
+   <input name='Other_antiviral_detail' type='TextInput'>
+    <pre_text>If yes, please specify</pre_text>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Clinical outcome</label>
+  <question>
+   <label>Was the case admitted to hospital?</label>
+   <input name='Admit_hospital' type='YesNo'>
+    <summary>Admitted to hospital</summary>
+   </input>
+   <input name='Admit_hosp_name' type='TextInput'>
+    <summary>At</summary>
+    <pre_text>If yes, please enter hospital name</pre_text>
+   </input>
+   <input name='Admit_hosp_location' type='TextInput'>
+    <pre_text>and the hospital location</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Please provide the admission and discharge dates</label>
+   <input name='Admit_date' type='DateInput'>
+    <summary>Admission date</summary>
+    <pre_text>Date admitted</pre_text>
+   </input>
+   <input name='Discharge_date' type='DateInput'>
+    <summary>Discharge date</summary>
+    <pre_text>Date discharged</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>Was the patient admitted/transferred to a second hospital?</label>
+   <input name='transfer' type='YesNo'>
+    <summary>Transferred/readmitted</summary>
+   </input>
+   <input name='Second_hospital' type='TextInput'>
+    <summary>Second hospital</summary>
+    <pre_text>If yes, please enter name of second hospital</pre_text>
+   </input>
+   <input name='Second_location' type='TextInput'>
+    <pre_text>and the hospital location</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>
+    Please provide the admission and discharge dates for the second hospital
+   </label>
+   <input name='Second_admit_date' type='DateInput'>
+    <summary>Second admission date</summary>
+    <pre_text>Date of admission</pre_text>
+   </input>
+   <input name='Second_discharge_date' type='DateInput'>
+    <summary>Second discharge date</summary>
+    <pre_text>Date of discharge</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>
+    Did the patient die from smallpox illness or any smallpox complications?
+   </label>
+   <input name='Death' type='YesNo' />
+   <input name='Death_date' type='DateInput'>
+    <summary>Date of death</summary>
+    <pre_text>If yes, please enter date of death</pre_text>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/spox_exposure.form b/forms/spox_exposure.form
new file mode 100644
index 0000000..6d98bf8
--- /dev/null
+++ b/forms/spox_exposure.form
@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+<form name='spox_exposure' form_type='case' allow_multiple='False'>
+ <label>Case exposure - personal (smallpox)</label>
+ <section>
+  <label>Source of illness identifiable</label>
+  <question>
+   <label>
+    Patient can identify the person from whom they caught their ilness. Please
+    record the details of the source individual.
+   </label>
+   <input name='Source_name' type='TextInput'>
+    <pre_text>Name of source of infection</pre_text>
+    <post_text>Surname, Given Names</post_text>
+   </input>
+   <input name='Source_address' type='TextInput'>
+    <pre_text>Address of source of infection</pre_text>
+    <post_text>Street address, town/suburb</post_text>
+   </input>
+   <input name='Source_postcode' type='IntInput'>
+    <pre_text>Postcode of source of infection</pre_text>
+   </input>
+   <input name='source_phone' type='IntInput'>
+    <pre_text>Telephone number of source of infection</pre_text>
+    <post_text>Please include area code</post_text>
+   </input>
+  </question>
+  <question>
+   <label>Date of last exposure to source of infection</label>
+   <input name='Source_date' type='DateInput' />
+  </question>
+  <question>
+   <label>Did the person have any of the following signs or symptoms?</label>
+   <input name='Source_signs' type='CheckBoxes'>
+    <choices>
+     <choice name='Papules'>Rash: papules/bumps</choice>
+     <choice name='Vesicles'>Rash: vesicles</choice>
+     <choice name='Pustules'>Rash: pustules (fluid filled)</choice>
+     <choice name='Scabs'>Rash: crusts/scabs</choice>
+     <choice name='Fever'>Fever</choice>
+     <choice name='Cough'>Cough</choice>
+     <choice name='Ill'>severely ill</choice>
+     <choice name='Immobile'>Immobile</choice>
+     <choice name='Other'>Other</choice>
+    </choices>
+   </input>
+   <input name='Source_other' type='TextInput'>
+    <pre_text>If other, please specify</pre_text>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Persons with similar illness</label>
+  <question>
+   <label>
+    Does the patient know anyone with an illness like theirs? Please record
+    the contact details of these people.
+   </label>
+   <input name='Similar_name' type='TextInput'>
+    <pre_text>Name of person</pre_text>
+    <post_text>Surname, Given Names</post_text>
+   </input>
+   <input name='Similar_address' type='TextInput'>
+    <pre_text>Address of person</pre_text>
+    <post_text>Street address, town/suburb</post_text>
+   </input>
+   <input name='Similar_postcode' type='IntInput'>
+    <pre_text>Postcode of person</pre_text>
+   </input>
+   <input name='Similar_phone' type='IntInput'>
+    <pre_text>Telephone number of person</pre_text>
+    <post_text>Please include area code</post_text>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>Other personal exposures</label>
+  <question>
+   <label>Exposure period (period of possible infection)</label>
+   <input name='Start_exposure' type='DateInput'>
+    <pre_text>Date 21 days before onset of rash</pre_text>
+   </input>
+   <input name='End_exposure' type='DateInput'>
+    <pre_text>Date 7 days before onset of rash</pre_text>
+   </input>
+  </question>
+  <question>
+   <label>
+    During the period between these 2 dates, were you in contact with anyone
+    who appeared to have chickenpox?
+   </label>
+   <input name='chickenpox' type='YesNo' />
+  </question>
+  <question>
+   <label>
+    During the period between these 2 dates, were you in contact with anyone
+    who had a severe rash on their face and/or arms?
+   </label>
+   <input name='Severe_rash' type='YesNo' />
+  </question>
+  <question>
+   <label>If yes, please provide their contact details</label>
+   <input name='Other_name' type='TextInput'>
+    <pre_text>Name of person</pre_text>
+    <post_text>Surname, Given Names</post_text>
+   </input>
+   <input name='Other_address' type='TextInput'>
+    <pre_text>Address of person</pre_text>
+    <post_text>Street address, town/suburb</post_text>
+   </input>
+   <input name='Other_postcode' type='IntInput'>
+    <pre_text>Postcode of person</pre_text>
+    <post_text>None</post_text>
+   </input>
+   <input name='Other_phone' type='IntInput'>
+    <pre_text>Telephone number of person</pre_text>
+    <post_text>Please include area code</post_text>
+   </input>
+  </question>
+  <question>
+   <label>Date of last exposure to this person</label>
+   <input name='Other_date' type='DateInput'>
+    <pre_text>None</pre_text>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/forms/spox_history.form b/forms/spox_history.form
new file mode 100644
index 0000000..f07ebca
--- /dev/null
+++ b/forms/spox_history.form
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<form name='spox_history' form_type='case' allow_multiple='False'>
+ <label>Past history (smallpox)</label>
+ <question>
+  <label>
+   Was  a smallpox vaccination administered prior to current outbreak?
+  </label>
+  <input name='Prio_vaccination' type='YesNo' required='True'>
+   <summary>Previous vaccination</summary>
+  </input>
+  <input name='Vaccine_doses' type='IntInput'>
+   <pre_text>If yes, how many doses?</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>In what year was the last dose administered?</label>
+  <input name='Vaccine_year' type='IntInput'>
+   <summary>Year of last vaccine dose</summary>
+   <pre_text>Year of last dose</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>
+   Was a smallpox vaccination administered during current outbreak?
+  </label>
+  <input name='Current_vaccine' type='YesNo' required='True' />
+  <input name='Current_vaccine_date' type='DateInput'>
+   <pre_text>If yes, date vaccine was given</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Is the patient pregnant?</label>
+  <input name='Pregnant' type='YesNo'>
+   <summary>Pregnant</summary>
+  </input>
+ </question>
+ <question>
+  <label>
+   Does the patient have a pre-existing immunocompromising condition? Eg
+   leukaemia, other cancers, HIV/Aids
+  </label>
+  <input name='Immuncomp' type='YesNo' />
+  <input name='Immuncomp_detail' type='TextInput'>
+   <summary>Immune compromising condition</summary>
+   <pre_text>If yes, please specify</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>
+   During the past month, has the patient taken any prescribed
+   immunocompromising or immunomodulating medications, including steroids?
+  </label>
+  <input name='Immundrug' type='YesNo' />
+  <input name='Immundrug_detail' type='TextInput'>
+   <summary>Immune compromising medication</summary>
+   <pre_text>If yes, please specify</pre_text>
+  </input>
+  <input name='Immundrug_condition' type='TextInput'>
+   <pre_text>If yes, for what condition/s</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>
+   In what setting did the patient think that they might have been exposed?
+  </label>
+  <input name='Exposure_setting' type='CheckBoxes'>
+   <summary>Suspected exposure setting</summary>
+   <choices>
+    <choice name='Community'>Community</choice>
+    <choice name='Correct'>Correctional facility</choice>
+    <choice name='Daycare'>Daycare</choice>
+    <choice name='Doctor'>Doctor's office</choice>
+    <choice name='Home'>Home</choice>
+    <choice name='Hospital'>Hospital</choice>
+    <choice name='Overseas'>Overseas travel</choice>
+    <choice name='Military'>Military</choice>
+    <choice name='School'>School</choice>
+    <choice name='Sport'>Sporting event</choice>
+    <choice name='University'>University or college</choice>
+    <choice name='Work'>Workplace</choice>
+    <choice name='Worship'>Place of worship</choice>
+    <choice name='Other'>Other</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+  <input name='Exposure_detail' type='TextInput'>
+   <pre_text>If other, please specify</pre_text>
+  </input>
+ </question>
+</form>
diff --git a/forms/spox_laboratory.form b/forms/spox_laboratory.form
new file mode 100644
index 0000000..b8d9c3c
--- /dev/null
+++ b/forms/spox_laboratory.form
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<form name='spox_laboratory' form_type='case' allow_multiple='True'>
+ <label>Lab test results (smallpox)</label>
+ <question>
+  <label>What type of specimen was collected for testing?</label>
+  <input name='Specimen_detail' type='TextInput'>
+   <summary>Specimens collected</summary>
+   <pre_text>If yes, please specify types of specimen</pre_text>
+   <post_text>None</post_text>
+  </input>
+ </question>
+ <question>
+  <label>On what date were the specimens collected?</label>
+  <input name='Specimen_date' type='DateInput'>
+   <pre_text>None</pre_text>
+  </input>
+ </question>
+ <section>
+  <label>Electron microscopy</label>
+  <question>
+   <label>
+    Was specimen examined microscopically for evidence of smallpox?
+   </label>
+   <input name='EM' type='YesNo'>
+    <summary>Electron microscopy</summary>
+   </input>
+  </question>
+  <question>
+   <label>Result from EM examination</label>
+   <input name='EM_result' type='RadioList'>
+    <summary>EM result</summary>
+    <choices>
+     <choice name='Positive'>Pox virus identified</choice>
+     <choice name='Negative'>Pox virus not identified</choice>
+     <choice name='Indeterminate'>Indeterminate</choice>
+    </choices>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label></label>
+ </section>
+</form>
diff --git a/forms/treatment_details.form b/forms/treatment_details.form
new file mode 100644
index 0000000..039deed
--- /dev/null
+++ b/forms/treatment_details.form
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<form name='treatment_details' form_type='case' allow_multiple='False'>
+ <label>Treatment Detail (SARS)</label>
+ <question>
+  <label>Recovered following treatment</label>
+  <input name='Recovery' type='YesNo' required='True'>
+   <summary>Recovered</summary>
+  </input>
+ </question>
+ <question>
+  <label>Antivirals (please list with dose and route of transmission)</label>
+  <input name='Antiviral1' type='TextInput'>
+   <pre_text>First antiviral agent</pre_text>
+  </input>
+  <input name='Antiviral2' type='TextInput'>
+   <pre_text>Second antiviral agent</pre_text>
+  </input>
+  <input name='Antiviral3' type='TextInput'>
+   <pre_text>Third antiviral agent</pre_text>
+  </input>
+  <input name='Antiviral4' type='TextInput'>
+   <pre_text>Fourth antiviral agent</pre_text>
+  </input>
+  <input name='Antiviral5' type='TextInput'>
+   <pre_text>Fifth antiviral agent</pre_text>
+  </input>
+  <input name='Antiviral_other' type='TextInput'>
+   <pre_text>Other antiviral agents</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>
+   Antimicrobials (Please list with dose and route of administration)
+  </label>
+  <input name='Antimicrobial1' type='TextInput'>
+   <pre_text>First antimicrobial agent</pre_text>
+  </input>
+  <input name='Antimicrobial2' type='TextInput'>
+   <pre_text>Second antimicrobial</pre_text>
+  </input>
+  <input name='Antimicrobial3' type='TextInput'>
+   <pre_text>Third antimicrobial</pre_text>
+  </input>
+  <input name='Antimicrobial4' type='TextInput'>
+   <pre_text>Fourth antimicrobial</pre_text>
+  </input>
+  <input name='Antimicrobial5' type='TextInput'>
+   <pre_text>Fifth antimicrobial</pre_text>
+  </input>
+  <input name='Antimicrobial_other' type='TextInput'>
+   <pre_text>Other antimicrobials</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Corticosteroids</label>
+  <input name='Corticosteroids' type='YesNo' />
+ </question>
+ <question>
+  <label>Outcome</label>
+  <input name='Outcome' type='RadioList'>
+   <choices>
+    <choice name='Alive'>Alive</choice>
+    <choice name='Dead'>Dead</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/httpinteract/LICENCE b/httpinteract/LICENCE
new file mode 100644
index 0000000..82e5e62
--- /dev/null
+++ b/httpinteract/LICENCE
@@ -0,0 +1,541 @@
+COPYRIGHT AND LICENSING ARRANGEMENTS
+
+All material is copyright 2006 Health Administration Corporation
+(New South Wales Department of Health) and others.
+
+NetEpi is licensed under the terms of the Health Administration
+Corporation Open Source Licence V1.2 (HACOS Licence V1.2), the complete
+text of which appears below.
+
+HEALTH ADMINISTRATION CORPORATION OPEN SOURCE LICENCE VERSION 1.2
+
+1. DEFINITIONS. 
+
+   "Commercial Use" shall mean distribution or otherwise making the
+   Covered Software available to a third party.
+
+   "Contributor" shall mean each entity that creates or contributes to
+   the creation of Modifications.
+
+   "Contributor Version" shall mean in case of any Contributor the
+   combination of the Original Software, prior Modifications used by a
+   Contributor, and the Modifications made by that particular Contributor
+   and in case of Health Administration Corporation in addition the
+   Original Software in any form, including the form as Executable.
+
+   "Covered Software" shall mean the Original Software or Modifications
+   or the combination of the Original Software and Modifications, in
+   each case including portions thereof.
+
+   "Electronic Distribution Mechanism" shall mean a mechanism generally
+   accepted in the software development community for the electronic
+   transfer of data.
+
+   "Executable" shall mean Covered Software in any form other than
+   Source Code.
+
+   "Initial Developer" shall mean the individual or entity identified as
+   the Initial Developer in the Source Code notice required by Exhibit A.
+
+   "Health Administration Corporation" shall mean the Health
+   Administration Corporation as established by the Health Administration
+   Act 1982, as amended, of the State of New South Wales, Australia. The
+   Health Administration Corporation has its offices at 73 Miller Street,
+   North Sydney, New South Wales 2059, Australia.
+
+   "Larger Work" shall mean a work, which combines Covered Software or
+   portions thereof with code not governed by the terms of this Licence.
+
+   "Licence" shall mean this document.
+
+   "Licensable" shall mean having the right to grant, to the maximum
+   extent possible, whether at the time of the initial grant or
+   subsequently acquired, any and all of the rights conveyed herein.
+
+   "Modifications" shall mean any addition to or deletion from the
+   substance or structure of either the Original Software or any previous
+   Modifications. When Covered Software is released as a series of files,
+   a Modification is:
+
+   a) Any addition to or deletion from the contents of a file
+      containing Original Software or previous Modifications.
+
+   b) Any new file that contains any part of the Original Software or
+      previous Modifications.
+
+   "Original Software" shall mean the Source Code of computer software
+   code which is described in the Source Code notice required by Exhibit
+   A as Original Software, and which, at the time of its release under
+   this Licence is not already Covered Software governed by this Licence.
+
+   "Patent Claims" shall mean any patent claim(s), now owned or hereafter
+   acquired, including without limitation, method, process, and apparatus
+   claims, in any patent Licensable by grantor.
+
+   "Source Code" shall mean the preferred form of the Covered Software
+   for making modifications to it, including all modules it contains,
+   plus any associated interface definition files, scripts used to
+   control compilation and installation of an Executable, or source
+   code differential comparisons against either the Original Software or
+   another well known, available Covered Software of the Contributor's
+   choice. The Source Code can be in a compressed or archival form,
+   provided the appropriate decompression or de-archiving software is
+   widely available for no charge.
+
+   "You" (or "Your") shall mean an individual or a legal entity exercising
+   rights under, and complying with all of the terms of, this Licence or
+   a future version of this Licence issued under Section 6.1. For legal
+   entities, "You" includes an entity which controls, is controlled
+   by, or is under common control with You. For the purposes of this
+   definition, "control" means (a) the power, direct or indirect,
+   to cause the direction or management of such entity, whether by
+   contract or otherwise, or (b) ownership of more than fifty per cent
+   (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. SOURCE CODE LICENCE. 
+
+2.1 Health Administration Corporation Grant. 
+
+Subject to the terms of this Licence, Health Administration Corporation
+hereby grants You a world-wide, royalty-free, non-exclusive licence,
+subject to third party intellectual property claims:
+
+a) under copyrights Licensable by Health Administration Corporation
+   to use, reproduce, modify, display, perform, sublicense and
+   distribute the Original Software (or portions thereof) with or without
+   Modifications, and/or as part of a Larger Work;
+
+b) and under Patents Claims infringed by the making, using or selling
+   of Original Software, to make, have made, use, practice, sell, and
+   offer for sale, and/or otherwise dispose of the Original Software
+   (or portions thereof).
+
+c) The licences granted in this Section 2.1(a) and (b) are effective
+   on the date Health Administration Corporation first distributes
+   Original Software under the terms of this Licence.
+
+d) Notwithstanding Section 2.1(b) above, no patent licence is granted:
+   1) for code that You delete from the Original Software; 2) separate
+   from the Original Software; or 3) for infringements caused by: i)
+   the modification of the Original Software or ii) the combination of
+   the Original Software with other software or devices.
+
+2.2 Contributor Grant. 
+
+Subject to the terms of this Licence and subject to third party
+intellectual property claims, each Contributor hereby grants You a
+world-wide, royalty-free, non-exclusive licence:
+
+a) under copyrights Licensable by Contributor, to use, reproduce,
+   modify, display, perform, sublicense and distribute the Modifications
+   created by such Contributor (or portions thereof) either on an
+   unmodified basis, with other Modifications, as Covered Software and/or
+   as part of a Larger Work; and
+
+b) under Patent Claims necessarily infringed by the making, using,
+   or selling of Modifications made by that Contributor either alone
+   and/or in combination with its Contributor Version (or portions of
+   such combination), to make, use, sell, offer for sale, have made,
+   and/or otherwise dispose of: 1) Modifications made by that Contributor
+   (or portions thereof); and 2) the combination of Modifications made
+   by that Contributor with its Contributor Version (or portions of
+   such combination).
+
+c) The licences granted in Sections 2.2(a) and 2.2(b) are effective
+   on the date Contributor first makes Commercial Use of the Covered
+   Software.
+
+d) Notwithstanding Section 2.2(b) above, no patent licence is granted:
+   1) for any code that Contributor has deleted from the Contributor
+   Version; 2) separate from the Contributor Version; 3) for infringements
+   caused by: i) third party modifications of Contributor Version or ii)
+   the combination of Modifications made by that Contributor with other
+   software (except as part of the Contributor Version) or other devices;
+   or 4) under Patent Claims infringed by Covered Software in the absence
+   of Modifications made by that Contributor.
+
+3. DISTRIBUTION OBLIGATIONS. 
+
+3.1 Application of Licence. 
+
+The Modifications which You create or to which You contribute are governed
+by the terms of this Licence, including without limitation Section
+2.2. The Source Code version of Covered Software may be distributed
+only under the terms of this Licence or a future version of this Licence
+released under Section 6.1, and You must include a copy of this Licence
+with every copy of the Source Code You distribute. You may not offer or
+impose any terms on any Source Code version that alters or restricts the
+applicable version of this Licence or the recipients' rights hereunder.
+
+3.2 Availability of Source Code. 
+
+Any Modification which You create or to which You contribute must be made
+available in Source Code form under the terms of this Licence either on
+the same media as an Executable version or via an accepted Electronic
+Distribution Mechanism to anyone to whom you made an Executable version
+available; and if made available via Electronic Distribution Mechanism,
+must remain available for at least twelve (12) months after the date it
+initially became available, or at least six (6) months after a subsequent
+version of that particular Modification has been made available to
+such recipients. You are responsible for ensuring that the Source Code
+version remains available even if the Electronic Distribution Mechanism
+is maintained by a third party.
+
+3.3 Description of Modifications. 
+
+You must cause all Covered Software to which You contribute to contain
+a file documenting the changes You made to create that Covered Software
+and the date of any change. You must include a prominent statement that
+the Modification is derived, directly or indirectly, from Original
+Software provided by Health Administration Corporation and including
+the name of Health Administration Corporation in (a) the Source Code,
+and (b) in any notice in an Executable version or related documentation
+in which You describe the origin or ownership of the Covered Software.
+
+3.4 Intellectual Property Matters 
+
+a) Third Party Claims. 
+
+   If Contributor has knowledge that a licence under a third party's
+   intellectual property rights is required to exercise the rights
+   granted by such Contributor under Sections 2.1 or 2.2, Contributor
+   must include a text file with the Source Code distribution titled
+   "LEGAL'' which describes the claim and the party making the claim
+   in sufficient detail that a recipient will know whom to contact. If
+   Contributor obtains such knowledge after the Modification is made
+   available as described in Section 3.2, Contributor shall promptly
+   modify the LEGAL file in all copies Contributor makes available
+   thereafter and shall take other steps (such as notifying appropriate
+   mailing lists or newsgroups) reasonably calculated to inform those
+   who received the Covered Software that new knowledge has been obtained.
+
+b) Contributor APIs. 
+
+   If Contributor's Modifications include an application programming
+   interface (API) and Contributor has knowledge of patent licences
+   which are reasonably necessary to implement that API, Contributor
+   must also include this information in the LEGAL file.
+
+c) Representations. 
+
+   Contributor represents that, except as disclosed pursuant to Section
+   3.4(a) above, Contributor believes that Contributor's Modifications are
+   Contributor's original creation(s) and/or Contributor has sufficient
+   rights to grant the rights conveyed by this Licence.
+
+3.5 Required Notices. 
+
+You must duplicate the notice in Exhibit A in each file of the Source
+Code. If it is not possible to put such notice in a particular Source
+Code file due to its structure, then You must include such notice in a
+location (such as a relevant directory) where a user would be likely to
+look for such a notice. If You created one or more Modification(s) You
+may add your name as a Contributor to the notice described in Exhibit
+A. You must also duplicate this Licence in any documentation for the
+Source Code where You describe recipients' rights or ownership rights
+relating to Covered Software. You may choose to offer, and to charge a
+fee for, warranty, support, indemnity or liability obligations to one or
+more recipients of Covered Software. However, You may do so only on Your
+own behalf, and not on behalf of Health Administration Corporation or any
+Contributor. You must make it absolutely clear that any such warranty,
+support, indemnity or liability obligation is offered by You alone,
+and You hereby agree to indemnify Health Administration Corporation and
+every Contributor for any liability incurred by Health Administration
+Corporation or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+3.6 Distribution of Executable Versions. 
+
+You may distribute Covered Software in Executable form only if the
+requirements of Sections 3.1-3.5 have been met for that Covered Software,
+and if You include a notice stating that the Source Code version of the
+Covered Software is available under the terms of this Licence, including
+a description of how and where You have fulfilled the obligations of
+Section 3.2. The notice must be conspicuously included in any notice in
+an Executable version, related documentation or collateral in which You
+describe recipients' rights relating to the Covered Software. You may
+distribute the Executable version of Covered Software or ownership rights
+under a licence of Your choice, which may contain terms different from
+this Licence, provided that You are in compliance with the terms of this
+Licence and that the licence for the Executable version does not attempt
+to limit or alter the recipient's rights in the Source Code version from
+the rights set forth in this Licence. If You distribute the Executable
+version under a different licence You must make it absolutely clear
+that any terms which differ from this Licence are offered by You alone,
+not by Health Administration Corporation or any Contributor. You hereby
+agree to indemnify Health Administration Corporation and every Contributor
+for any liability incurred by Health Administration Corporation or such
+Contributor as a result of any such terms You offer.
+
+3.7 Larger Works. 
+
+You may create a Larger Work by combining Covered Software with other
+software not governed by the terms of this Licence and distribute the
+Larger Work as a single product. In such a case, You must make sure the
+requirements of this Licence are fulfilled for the Covered Software.
+
+4. INABILITY TO COMPLY DUE TO STATUTE OR REGULATION. 
+
+If it is impossible for You to comply with any of the terms of this
+Licence with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with the
+terms of this Licence to the maximum extent possible; and (b) describe the
+limitations and the code they affect. Such description must be included
+in the LEGAL file described in Section 3.4 and must be included with all
+distributions of the Source Code.  Except to the extent prohibited by
+statute or regulation, such description must be sufficiently detailed
+for a recipient of ordinary skill to be able to understand it.
+
+5. APPLICATION OF THIS LICENCE. 
+
+This Licence applies to code to which Health Administration Corporation
+has attached the notice in Exhibit A and to related Covered Software.
+
+6. VERSIONS OF THE LICENCE. 
+
+6.1 New Versions. 
+
+Health Administration Corporation may publish revised and/or new
+versions of the Licence from time to time. Each version will be given
+a distinguishing version number.
+
+6.2 Effect of New Versions. 
+
+Once Covered Software has been published under a particular version
+of the Licence, You may always continue to use it under the terms of
+that version. You may also choose to use such Covered Software under
+the terms of any subsequent version of the Licence published by Health
+Administration Corporation. No one other than Health Administration
+Corporation has the right to modify the terms applicable to Covered
+Software created under this Licence.
+
+7. DISCLAIMER OF WARRANTY. 
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENCE ON AN "AS IS'' BASIS,
+WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS
+WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU
+(NOT HEALTH ADMINISTRATION CORPORATION, ITS LICENSORS OR AFFILIATES OR
+ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR
+OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
+OF THIS LICENCE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER
+EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION. 
+
+8.1 This Licence and the rights granted hereunder will terminate
+automatically if You fail to comply with terms herein and fail to
+cure such breach within 30 days of becoming aware of the breach. All
+sublicences to the Covered Software which are properly granted shall
+survive any termination of this Licence. Provisions which, by their
+nature, must remain in effect beyond the termination of this Licence
+shall survive.
+
+8.2 If You initiate litigation by asserting a patent infringement claim
+(excluding declatory judgment actions) against Health Administration
+Corporation or a Contributor (Health Administration Corporation
+or Contributor against whom You file such action is referred to as
+"Participant") alleging that:
+
+a) such Participant's Contributor Version directly or indirectly
+   infringes any patent, then any and all rights granted by such
+   Participant to You under Sections 2.1 and/or 2.2 of this Licence
+   shall, upon 60 days notice from Participant terminate prospectively,
+   unless if within 60 days after receipt of notice You either: (i)
+   agree in writing to pay Participant a mutually agreeable reasonable
+   royalty for Your past and future use of Modifications made by such
+   Participant, or (ii) withdraw Your litigation claim with respect to
+   the Contributor Version against such Participant. If within 60 days
+   of notice, a reasonable royalty and payment arrangement are not
+   mutually agreed upon in writing by the parties or the litigation
+   claim is not withdrawn, the rights granted by Participant to
+   You under Sections 2.1 and/or 2.2 automatically terminate at the
+   expiration of the 60 day notice period specified above.
+
+b) any software, hardware, or device, other than such Participant's
+   Contributor Version, directly or indirectly infringes any patent,
+   then any rights granted to You by such Participant under Sections
+   2.1(b) and 2.2(b) are revoked effective as of the date You first
+   made, used, sold, distributed, or had made, Modifications made by
+   that Participant.
+
+8.3 If You assert a patent infringement claim against Participant
+alleging that such Participant's Contributor Version directly or
+indirectly infringes any patent where such claim is resolved (such as by
+licence or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licences granted by such
+Participant under Sections 2.1 or 2.2 shall be taken into account in
+determining the amount or value of any payment or license.  
+
+8.4 In the event of termination under Sections 8.1 or 8.2 above, all
+end user licence agreements (excluding distributors and resellers) which
+have been validly granted by You or any distributor hereunder prior to
+termination shall survive termination.
+
+9. LIMITATION OF LIABILITY. 
+
+9.1 UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, HEALTH
+ADMINISTRATION CORPORATION, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR
+OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE
+TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
+ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE
+BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, BUT MAY ALLOW
+LIABILITY TO BE LIMITED; IN SUCH CASES, A PARTY'S, ITS EMPLOYEES',
+LICENSORS' OR AFFILIATES' LIABILITY SHALL BE LIMITED TO AUD$100. NOTHING
+CONTAINED IN THIS LICENCE SHALL PREJUDICE THE STATUTORY RIGHTS OF ANY
+PARTY DEALING AS A CONSUMER.
+
+9.2 Notwithstanding any other clause in the licence, and to the extent
+permitted by law:
+
+(a) Health Administration Corporation ("the Corporation") excludes all
+    conditions and warranties which would otherwise be implied into
+    a supply of goods or services arising out of or in relation to
+    the granting of this licence by the Corporation or any associated
+    acquisition of software to which this licence relates;
+
+(b) Where a condition or warranty is implied into such a supply and
+    that condition or warranty cannot be excluded by law that warranty
+    or condition is implied into that supply and the liability of the
+    Health Administration Corporation for a breach of that condition or
+    warranty is limited to the fullest extent permitted by law and, in
+    respect of conditions and warranties implied by the Trade Practices
+    Act (Commonwealth of Australia) 1974, is limited, to the extent
+    permitted by law, to one or more of the following at the election
+    of the Corporation:
+
+    (A) In the case of goods: (i) the replacement of the goods or the
+        supply of equivalent goods; (ii) the repair of the goods; (iii)
+        the payment of the cost of replacing the goods or of acquiring
+        equivalent goods; (iv) the payment of the cost of having the
+        goods repaired; and
+
+    (B) in the case of services: (i) the supplying of the services again;
+        or (ii) the payment of the cost of having the services supplied
+        again.
+
+10. MISCELLANEOUS. 
+
+This Licence represents the complete agreement concerning subject matter
+hereof. All rights in the Covered Software not expressly granted under
+this Licence are reserved. Nothing in this Licence shall grant You any
+rights to use any of the trademarks of Health Administration Corporation
+or any of its Affiliates, even if any of such trademarks are included
+in any part of Covered Software and/or documentation to it.
+
+This Licence is governed by the laws of the State of New South Wales,
+Australia excluding its conflict-of-law provisions. All disputes or
+litigation arising from or relating to this Agreement shall be subject
+to the jurisdiction of the Supreme Court of New South Wales. If any part
+of this Agreement is found void and unenforceable, it will not affect
+the validity of the balance of the Agreement, which shall remain valid
+and enforceable according to its terms.
+
+11. RESPONSIBILITY FOR CLAIMS. 
+
+As between Health Administration Corporation and the Contributors,
+each party is responsible for claims and damages arising, directly or
+indirectly, out of its utilisation of rights under this Licence and You
+agree to work with Health Administration Corporation and Contributors
+to distribute such responsibility on an equitable basis. Nothing herein
+is intended or shall be deemed to constitute any admission of liability.
+
+EXHIBIT A 
+
+The contents of this file are subject to the HACOS Licence Version 1.2
+(the "Licence"); you may not use this file except in compliance with
+the Licence.
+
+Software distributed under the Licence is distributed on an "AS IS"
+basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+Licence for the specific language governing rights and limitations under
+the Licence.
+
+The Original Software is "NetEpi". The Initial Developer of the Original
+Software is the Health Administration Corporation, incorporated in the
+State of New South Wales, Australia.
+
+Copyright (C) 2006 Health Administration Corporation
+All Rights Reserved.
+
+APPENDIX 1. DIFFERENCES BETWEEN THE HACOS LICENCE VERSION 1.2, THE
+MOZILLA PUBLIC LICENSE VERSION 1.1 AND THE NOKIA OPEN SOURCE LICENSE
+(NOKOS LICENSE) VERSION 1.0A
+
+The HACOS Licence Version 1.2 was derived from the Mozilla Public
+License Version 1.1 using some of the changes to the Mozilla Public
+License embodied in the Nokia Open Source License (NOKOS License)
+Version 1.0a. The differences between the HACOS Licence Version 1.2
+(this document), the Mozilla Public License and the NOKOS License are
+as follows:
+
+i.    The title of the licence was changed to "Health Administration
+      Corporation Open Source Licence Version 1.2".
+
+ii.   Globally, all references to "Netscape Communications Corporation",
+      "Mozilla", "Nokia" and "Nokia Corporation" were changed to "Health
+      Administration Corporation".
+
+iii.  Globally, the words "means", "Covered Code" and "Covered Software"
+      as used in the Mozilla Public License were changed to "shall means",
+      "Covered Code" and "Covered Software" respectively, as used in
+      the NOKOS License.
+
+iv.   In Section 1 (Definitions), a definition of "Health Administration
+      Corporation" was added.
+
+v.    In Section 2, the term "intellectual property rights" used in the
+      Mozilla Public License was replaced by the term "copyrights"
+      as used in the NOKOS License.
+
+vi.   In Section 2.2 (Contributor Grant), the words "Subject to the
+      terms of this License" which appear in the NOKOS License were
+      added to the Mozilla Public License.
+
+vii.  The sentence "However, You may include an additional document
+      offering the additional rights described in Section 3.5." which
+      appears in the Mozilla Public License was omitted.
+
+viii. Section 6.3 (Derivative Works) of the Mozilla Public License,
+      which permits modifications to the Mozilla Public License,
+      was omitted.
+
+ix.   The original Section 9 (Limitation of Liability) was renumbered
+      as Section 9.1, a maximum liability of AUD$100 was specified
+      for those jurisdictions which do not allow complete exclusion of
+      liability but which do allow limitation of liability. The sentence
+      "NOTHING CONTAINED IN THE LICENSE SHALL PREJUDICE THE STATUTORY
+      RIGHTS OF ANY PARTY DEALING AS A CONSUMER.", which appears in the
+      NOKOS License but not in the Mozilla Public License, was added.
+
+x.    Section 9.2 was added in order to further limit liability to the
+      maximum extent permitted by the Commonwealth of Australia Trade
+      Practices Act 1974.
+
+xi.   Section 10 of the Mozilla Public License, which provides additional
+      conditions for United States Government End Users, was omitted.
+
+xii.  The governing law and jurisdiction for the settlement of disputes
+      in Section 11 of the Mozilla Public License and Section 10 of the
+      NOKOS License was changed to the laws of the State of New South
+      Wales and the Supreme Court of New South Wales respectively. The
+      exclusion of the application of the United Nations Convention on
+      Contracts for the International Sale of Goods which appears in
+      the Mozilla Public License was omitted.
+
+xiii. Section 13 (Multiple-Licensed Code) of the Mozilla Public License 
+      was omitted.
+
+xiv.  The provisions for alternative licensing arrangement for contributed
+      code which appear in Exhibit A of the Mozilla Public License 
+      were omitted.
+
diff --git a/httpinteract/README b/httpinteract/README
new file mode 100644
index 0000000..b7c7a71
--- /dev/null
+++ b/httpinteract/README
@@ -0,0 +1,54 @@
+NetEpi Network Interactivity Tester
+===================================
+
+LICENSE
+=======
+
+All material associated with "NetEpi" is Copyright (C) 2004-2006 Health
+Administration Corporation (New South Wales Department of Health) and
+others (see the CONTRIBUTORS file for details).
+
+NetEpi is licensed under the terms of the Health Administration
+Corporation Open Source License Version 1.2 (HACOS License V1.2), the
+full text of which can be found in the LICENSE file provided with NetEpi.
+
+PREREQUISITES
+=============
+
+Python 2.3 or above
+
+    http://www.python.org/
+
+PyPgSQL:
+
+    http://pypgsql.sourceforge.net/
+
+Mochikit (version 1.2 or above)
+
+    http://mochikit.com/
+
+INSTALLATION
+============
+
+Install MochiKit
+
+    Unpack tarball, copy the directory packed/MochiKit and it's contents
+    to the root of your system htdocs directory (it should be accessible
+    as http://website_address/MochiKit/MochiKit.js).
+
+Install httpinteract:
+
+    Make a directory "httpinteract" in your system htdocs directory,
+    and copy the following files to it:
+
+        httpinteract.css
+        httpinteract.js
+        index.html
+
+    Copy httpinteract.cgi to your system cgi-bin directory and check
+    it has execute and read permissions for your web user. You may wish
+    to edit this file and change the "sleep", "motd", "server_send" or
+    "client_send" parameters.
+
+    Edit "schema", check the webuser variable is correct for your system, 
+    then run the script as a DBA user (probably root).
diff --git a/httpinteract/gnuplot.py b/httpinteract/gnuplot.py
new file mode 100644
index 0000000..748f866
--- /dev/null
+++ b/httpinteract/gnuplot.py
@@ -0,0 +1,44 @@
+# Very quick and dirty driver for gnuplot
+
+import os
+import re
+from pyPgSQL import PgSQL
+
+def clean(x):
+    return re.sub('[^-a-zA-Z0-9]', '_', x)
+
+n = 0
+files = {}
+db = PgSQL.connect(database='httpinteract')
+curs = db.cursor()
+try:
+    curs.execute('SELECT started, siteinfo, elapsed FROM reports')
+    for row in curs.fetchall():
+        siteinfo = clean(row.siteinfo)
+        try:
+            f = files[siteinfo]
+        except KeyError:
+            f = files[siteinfo] = open('siteinfo_' + siteinfo, 'w')
+            n += 1
+        f.write('%s %s\n' % (row.started, row.elapsed))
+finally:
+    curs.close()
+# Must explicitly close DB, or no exclusive locks can occur.
+db.close()
+lines = []
+for n, f in files.items():
+    f.close()
+    lines.append('"siteinfo_%s" using 1:3' % n)
+cmds = r"""\
+set timefmt "%Y-%m-%d %H:%M:%S"
+set logscale y
+set xdata time
+plot """ + ', '.join(lines)
+
+f = os.popen('gnuplot -', 'w')
+print >> f, cmds
+f.flush()
+raw_input()
+
+for n in files.keys():
+    os.unlink('siteinfo_' + n)
diff --git a/httpinteract/httpinteract.cgi b/httpinteract/httpinteract.cgi
new file mode 100755
index 0000000..007e9f3
--- /dev/null
+++ b/httpinteract/httpinteract.cgi
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Case Manager". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2006 Health Administration Corporation.
+#   All Rights Reserved.
+#
+# $Source: /usr/local/cvsroot/NSWDoH/httpinteract/httpinteract.cgi,v $
+# $Id: httpinteract.cgi,v 1.7 2006/04/21 04:55:27 andrewm Exp $
+
+import sys, os
+from datetime import datetime
+from pyPgSQL import PgSQL
+
+server_send = 30 * 1024                 # Bytes to send to client
+client_send = 4 * 1024                  # Bytes sent by client to us
+interval = 60                           # Time between tests in seconds
+motd = '''\
+Thankyou for participating in this test.
+'''                                     # Message to users
+
+database = dict(database='httpinteract')
+table = 'reports'
+
+server_junk = 'X' * server_send
+
+
+client_data = sys.stdin.read()
+
+if 0:
+    f = open('/tmp/x', 'w')
+    for k, v in os.environ.items():
+        f.write('%20s: %s\n' % (k, v))
+    f.write('%r\n' % client_data)
+    f.close()
+
+print """\
+Content-type: text/plain
+
+{
+    "interval": %(interval)r,
+    "sendcount": %(client_send)r,
+    "motd": %(motd)r,
+    "junk": %(server_junk)r
+}
+""" % vars()
+sys.stdout.close()
+
+class Updater:
+    cols = ('started', 'siteinfo', 'status', 'elapsed', 'received', 
+              'srcaddr', 'forwarded', 'useragent')
+    keys = 'started', 'siteinfo'
+    table = table
+
+    def execute(self, curs, cmd, *args):
+        try:
+            curs.execute(cmd, args)
+        except PgSQL.DatabaseError, e:
+            raise e.__class__('%s%s' % (e, cmd % args))
+
+    def update(self, curs, vars):
+        args = []
+        s, k = [], []
+        for col in self.cols:
+            if col in vars:
+                args.append(vars[col])
+                s.append('%s=%%s' % col)
+        for key in self.keys:
+            args.append(vars[key])
+            k.append('%s=%%s' % key)
+        cmd = 'UPDATE %s SET %s WHERE %s' % (table, ', '.join(s), 
+                                             ' AND '.join(k))
+        self.execute(curs, cmd, *args)
+
+    def insert(self, curs, vars):
+        args = [vars.get(col) for col in self.cols]
+        s = ['%s'] * len(self.cols)
+        cmd = 'INSERT INTO %s (%s) VALUES (%s)' % (table, ', '.join(self.cols), 
+                                                   ', '.join(s))
+        self.execute(curs, cmd, *args)
+
+    def updateinsert(self, vars):
+        curs = db.cursor()
+        try:
+            self.execute(curs, 'LOCK TABLE %s' % table)
+            self.update(curs, vars)
+            if curs.rowcount == 0:
+                self.insert(curs, vars)
+        finally:
+            curs.close()
+
+
+db = PgSQL.connect(**database)
+try:
+    siteinfo = None
+    for line in client_data.splitlines():
+        if siteinfo is None:
+            siteinfo = line
+        elif line == '-- ':
+            break
+        else:
+            vars = {}
+            fields = line.split(',')
+            vars['started'] = str(datetime.fromtimestamp(float(fields[0])/1000))
+            vars['siteinfo'] = siteinfo
+            vars['status'] = fields[1]
+            try:
+                vars['elapsed'] = float(fields[2]) / 1000
+            except ValueError:
+                pass
+            try:
+                vars['received'] = int(fields[3])
+            except ValueError:
+                pass
+            vars['srcaddr'] = os.environ.get('REMOTE_ADDR')
+            vars['forwarded'] = (os.environ.get('HTTP_X_FORWARDED_FOR') or
+                                os.environ.get('HTTP_FORWARDED'))
+            vars['useragent'] = os.environ.get('HTTP_USER_AGENT')
+            Updater().updateinsert(vars)
+except PgSQL.DatabaseError, e:
+    db.rollback()
+    print >> sys.stderr, 'update failed: %s' % e
+else:
+    db.commit()
diff --git a/httpinteract/httpinteract.css b/httpinteract/httpinteract.css
new file mode 100644
index 0000000..ada5de5
--- /dev/null
+++ b/httpinteract/httpinteract.css
@@ -0,0 +1,86 @@
+body {
+    background: white;
+    white-space: normal;
+}
+h1 {
+    text-align: center;
+    background: #eee;
+    margin: 0;
+    border-bottom: 2px solid #cce;
+}
+.motd {
+    display: block;
+    margin: 2ex;
+    text-align: center;
+    background: #eee;
+    border: 2px solid #cce;
+}
+    
+.controls {
+    display: block;
+    float: right;
+    margin: 2ex;
+}
+.controls input {
+    width: 12ex;
+    display: block;
+}
+.params {
+    margin: 2ex;
+}
+.params .prm {
+    margin: 2px;
+}
+.params label {
+    width: 20%;
+    display: block;
+    float: left;
+    background-color: #eee;
+    border-right: 2px solid #cce;
+    margin-right: 1ex;
+}
+.params input {
+    width: 50%;
+}
+#results {
+    margin: 2ex;
+}
+#results table {
+    border: 1px solid #ccc;
+    empty-cells: show;
+}
+#results th {
+    background-color: #eee;
+}
+#results td {
+    border: 1px solid #ccc;
+    padding-left: 0.5ex;
+    padding-right: 0.5ex;
+}
+#stats {
+    display: block;
+    float: right;
+    margin: 2ex;
+    border: 1px solid #cce;
+}
+#stats td {
+    padding-left: 0.5ex;
+    padding-right: 0.5ex;
+}
+#log {
+    margin: 2ex;
+    background-color: #eec;
+}
+#log h2 {
+    margin: 0;
+    background-color: #ddb;
+}
+.hidden {
+    display: none;
+}
+.notice {
+    background-color: #fc4;
+}
+.error {
+    background-color: #f44;
+}
diff --git a/httpinteract/httpinteract.js b/httpinteract/httpinteract.js
new file mode 100644
index 0000000..018e6cb
--- /dev/null
+++ b/httpinteract/httpinteract.js
@@ -0,0 +1,270 @@
+//
+//   The contents of this file are subject to the HACOS License Version 1.2
+//   (the "License"); you may not use this file except in compliance with
+//   the License.  Software distributed under the License is distributed
+//   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+//   implied. See the LICENSE file for the specific language governing
+//   rights and limitations under the License.  The Original Software
+//   is "NetEpi Case Manager". The Initial Developer of the Original
+//   Software is the Health Administration Corporation, incorporated in
+//   the State of New South Wales, Australia.
+//
+//   Copyright (C) 2006 Health Administration Corporation.
+//   All Rights Reserved.
+//
+// $Source: /usr/local/cvsroot/NSWDoH/httpinteract/httpinteract.js,v $
+// $Id: httpinteract.js,v 1.7 2006/04/21 07:48:46 andrewm Exp $
+
+running = null;
+refresh = null;
+flasher = null;
+results = [];
+histCount = 10;
+requestCount = 0;
+responseCount = 0;
+// Logger.maxSize is broken in MochiKit 1.2
+// logger.maxSize = 100;
+cumTime = 0;
+minTime = undefined;
+maxTime = undefined;
+junk = "X";
+
+var updateCell = function (elem, text, className)
+{
+    if (typeof(elem) == 'string')
+        elem = getElement(elem);
+    if (elem && text) {
+        if (!elem.firstChild || elem.firstChild.textNode != text)
+            replaceChildNodes(elem, text);
+        if (elem.className != className)
+            elem.className = className ? className : '';
+    }
+}
+
+tableRows = [];
+var initResultsTable = function () {
+    var elem = getElement('results');
+    if (elem) {
+        tableRows.push(TR(null,
+            TH(null, 'Initiated'),
+            TH(null, 'Status'),
+            TH(null, 'Elapsed'),
+            TH(null, 'Received')))
+        for (var i = 0; i < histCount; ++i)
+            tableRows.push(TR(null, 
+                TD(null, 'n/a'), TD(null, 'n/a'), 
+                TD(null, 'n/a'), TD(null, 'n/a')))
+        replaceChildNodes(elem, TABLE(null, TBODY(null, tableRows)));
+    }
+}
+
+var prettyMS = function (t)
+{
+    if (t)
+        return (t / 1000.0).toFixed(2) + ' secs';
+}
+
+var updateResults = function () {
+    var now = new Date();
+    var rowCount = 1;
+    if (refresh)
+        refresh.cancel();
+    for (var i = results.length - 1; i >= 0; --i) {
+        var info = results[i];
+        var row = tableRows[rowCount++];
+        var status_class;
+        var elapsed = '';
+        switch (info.status) {
+        case 'pending':
+            status_class = 'notice';
+            elapsed = prettyMS(now.getTime() - info.started.getTime());
+            break;
+        case 'failed':
+            status_class = 'error';
+            break;
+        case 'completed':
+            status_class = '';
+            elapsed = prettyMS(info.elapsed);
+            break;
+        }
+        updateCell(row.cells[0], toISOTimestamp(info.started));
+        updateCell(row.cells[1], info.status, status_class);
+        updateCell(row.cells[2], elapsed);
+        updateCell(row.cells[3], info.received ? info.received : '');
+    }
+    updateCell("stat_requests", requestCount);
+    updateCell("stat_responses", responseCount);
+    if (requestCount > 0)
+        updateCell("stat_rate", (responseCount * 100.0 / requestCount).toFixed(2) + '%');
+    updateCell("stat_minimum", prettyMS(minTime));
+    updateCell("stat_maximum", prettyMS(maxTime));
+    if (responseCount)
+        updateCell("stat_average", prettyMS(cumTime / responseCount));
+    refresh = callLater(10, updateResults);
+}
+
+var toggleLog = function (id, button) {
+    var elem = getElement(id);
+    if (elem) {
+        if (hasElementClass(elem, "hidden")) {
+            removeElementClass(elem, "hidden");
+            button.value = button.value.replace('View', 'Hide');
+        } else {
+            addElementClass(elem, "hidden");
+            button.value = button.value.replace('Hide', 'View');
+        }
+    }
+}
+var stopStartTester = function (button) {
+    if (running && running.fired == -1) {
+        stop();
+        button.value = 'Start';
+    } else {
+        start();
+        button.value = 'Stop';
+    }
+
+}
+var requestSuccess = function (info, req) {
+    var now = new Date()
+    info.elapsed = now.getTime() - info.started.getTime();
+    info.status = 'completed';
+    cumTime += info.elapsed;
+    ++responseCount;
+    if (minTime === undefined || info.elapsed < minTime)
+        minTime = info.elapsed;
+    if (maxTime === undefined || info.elapsed > maxTime)
+        maxTime = info.elapsed;
+    var form = document.forms.params_form;
+    var data = evalJSONRequest(req);
+    if (form) {
+        if (data.url)
+            form.server.value = data.url;
+        if (data.sendcount)
+            form.sendcount.value = data.sendcount;
+        if (data.interval)
+            form.interval.value = (data.interval / 60.0).toFixed(3);
+        if (data.motd)
+            updateCell("motd", data.motd, "motd");
+        else
+            updateCell("motd", "", "hidden");
+        if (data.junk)
+            info.received = data.junk.length;
+    }
+    updateResults();
+}
+var requestFailure = function (info, err) {
+    log(Date(), "async request failed", err);
+    info.status = 'failed';
+    updateResults();
+}
+var makeClientReport = function () {
+    var lines = [];
+    var form = document.forms.params_form;
+    if (!form) return;
+    lines.push(form.siteinfo.value);
+    // Skip the current one as it will always be "pending"
+    for (var i = results.length - 2; i >= 0; --i) {
+        var info = results[i];
+        lines.push([
+            info.started.getTime(), 
+            info.status,
+            info.elapsed,
+            info.received
+        ].join(','));
+    }
+    lines.push('-- ');
+    return lines;
+}
+var nullFn = function () {};
+
+var getOnReadyStateChangeClosure = function (req, ctx) {
+    return function () {
+        if (req.readyState == 4) {
+            var status = req.status;
+            req.onreadystatechange = nullFn;
+            if (status == 200 || status == 304)
+                requestSuccess(ctx, req);
+            else
+                requestFailure(ctx, req.status);
+        }
+    }
+}
+
+var makeAsyncReq = function () {
+    var form = document.forms.params_form;
+    if (!form) return;
+    var url = form.server.value;
+    var interval = form.interval.value * 60.0;
+    running = callLater(interval, makeAsyncReq);
+    if (junk.length != form.sendcount.value) {
+        while (junk.length < form.sendcount.value)
+            junk = junk + junk;
+        junk = junk.slice(0, form.sendcount.value);
+    }
+    var now = new Date();
+    var info = {
+        'started': now, 
+        'status': 'pending',
+        'elapsed': undefined,
+        'received': undefined
+    };
+    results.push(info);
+    if (results.length > histCount)
+        results = results.slice(results.length - histCount);
+    var report = makeClientReport();
+    report.push(junk);
+    var req = getXMLHttpRequest();
+    req.open('POST', url, true);
+    req.onreadystatechange = getOnReadyStateChangeClosure(req, info);
+    req.send(report.join('\n'));
+    /* 
+     * sendXMLHttpRequest appears to be leaking references under IE - suspect
+     * it's use of continuations (sendContent certainly lives longer than it
+     * needs to) - disabled for now.
+    var d = sendXMLHttpRequest(req,report.join('\n')); 
+    d.addCallback(requestSuccess, info);
+    d.addErrback(requestFailure, info);
+    */
+    ++requestCount;
+    updateResults();
+}
+var flashSiteInfo = function () {
+    elem = getElement('siteinfo');
+    if (!elem.value) {
+        if (!flasher || flasher.fired != -1) {
+            toggleElementClass('notice', elem);
+            elem.focus();
+            flasher = callLater(2, flashSiteInfo);
+        }
+    } else {
+        removeElementClass(elem, "notice");
+    }
+}
+var stop = function () {
+    if (running) {
+        running.cancel();
+        log(Date(), "Tester stopped");
+    }
+    if (refresh)
+        refresh.cancel();
+    updateCell("stat_running", 'STOPPED', 'notice');
+}
+var start = function () {
+    var form = document.forms.params_form;
+    if (form.siteinfo.value) {
+        log(Date(), "Tester starting");
+        makeAsyncReq();
+        updateCell("stat_running", 'Yes');
+    } else {
+        stop();
+        flashSiteInfo();
+    }
+}
+
+var interactTesterOnLoad = function () {
+    createLoggingPane(true);
+    initResultsTable();
+    start();
+}
+addLoadEvent(interactTesterOnLoad);
diff --git a/httpinteract/httpinteract_soomload.py b/httpinteract/httpinteract_soomload.py
new file mode 100644
index 0000000..b50f160
--- /dev/null
+++ b/httpinteract/httpinteract_soomload.py
@@ -0,0 +1,177 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Case Manager". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2006 Health Administration Corporation.
+#   All Rights Reserved.
+#
+
+"""
+Load httpinteract timings into SOOM
+"""
+
+# Standard Python
+import os
+from optparse import OptionParser
+
+# 3rd Party Modules
+import mx.DateTime
+
+# SOOM modules
+from SOOMv0 import *
+
+sitemap = None
+def load_sitemap(filename):
+    import csv
+    return dict(csv.reader(open(filename, 'rb')))
+
+def make_httptimings(options):
+    httptimings = makedataset(options.dsname, label=options.dslabel)
+    httptimings.addcolumn("started", label="Start time", 
+                          coltype="ordinal", datatype="datetime")
+    httptimings.addcolumn("siteinfo", label="Site", 
+                          coltype="categorical", datatype='recode')
+    httptimings.addcolumn("status", label="Transaction status", 
+                          coltype="categorical", datatype='recode')
+    httptimings.addcolumn("elapsed", label="Elapsed time", 
+                          coltype="scalar", datatype=float)
+    httptimings.addcolumn("srcaddr", label="Source IP address", 
+                          coltype="categorical", datatype='recode')
+    httptimings.addcolumn("fwdaddr", label="Forwarded IP address", 
+                          coltype="categorical", datatype='recode')
+    httptimings.addcolumn("useragent", label="Type of browser", 
+                          coltype="categorical", datatype='recode')
+    if options.verbose:
+        print httptimings
+    return httptimings
+
+# started,siteinfo,status,elapsed,received,srcaddr,forwarded,useragent
+
+def httptimings_CSV_source(options, **kwargs):
+    httptimings_columns = [
+        DataSourceColumn("started",ordinalpos=0,format="yyyy-mm-dd HH:MM:SS"),
+        DataSourceColumn("siteinfo",ordinalpos=1),
+        DataSourceColumn("status",ordinalpos=2),
+        DataSourceColumn("elapsed",ordinalpos=3),
+        DataSourceColumn("srcaddr",ordinalpos=5),
+        DataSourceColumn("fwdaddr",ordinalpos=6),
+        DataSourceColumn("useragent",ordinalpos=7),
+    ]
+
+    from SOOMv0.Sources.CSV import CSVDataSource
+    return CSVDataSource("httptimings_data", httptimings_columns, 
+                         filename=options.filename, header_rows=1, 
+                         delimiter="|", **kwargs)
+
+def httptimings_DB_source(options, **kwargs):
+    httptimings_columns = [
+        DataSourceColumn("started"),
+        DataSourceColumn("siteinfo"),
+        DataSourceColumn("status"),
+        DataSourceColumn("elapsed"),
+        DataSourceColumn("srcaddr"),
+        DataSourceColumn("fwdaddr", dbname='forwarded'),
+        DataSourceColumn("useragent"),
+    ]
+
+    try:
+        import psycopg2 as dbapi
+        import psycopg2.extensions
+        import _psycopg
+        psycopg2.extensions.new_type((1114, 1184, 704, 1186),
+                                      'DATETIME', _psycopg.MXDATETIME)
+        psycopg2.extensions.new_type((1083, 1266),
+                                      'TIME', _psycopg.MXTIME)
+        psycopg2.extensions.new_type((1082,),
+                                      'DATE', _psycopg.MXDATE)
+        psycopg2.extensions.new_type((704, 1186),
+                                      'INTERVAL', _psycopg.MXINTERVAL)
+    except ImportError:
+        from pyPgSQL import PgSQL as dbapi
+    from SOOMv0.Sources.DB import DBDataSource
+
+    db = dbapi.connect(database=options.database)
+    return DBDataSource("httptimings_data", httptimings_columns, db,
+                        options.table, **kwargs)
+
+def der_dt(start_date_time):
+    data = [None] * len(start_date_time)
+    rel = mx.DateTime.RelativeDateTime(minute=0, second=0)
+    for i in xrange(len(start_date_time)):
+        if start_date_time[i]:
+            data[i] = start_date_time[i] + rel
+    return data, None
+
+
+def http_xform(row_dict):
+    siteinfo = row_dict['siteinfo'].strip()
+    row_dict['siteinfo'] = sitemap.get(siteinfo, siteinfo)
+    return row_dict
+
+
+def load_httptimings(ds, options):
+    source_args = {}
+    if options.sitemap:
+        global sitemap
+        sitemap = load_sitemap(options.sitemap)
+        source_args['xformpre'] = http_xform
+    httpdata = httptimings_DB_source(options)
+    if options.verbose:
+        print httpdata
+    ds.initialise()
+    ds.loaddata(httpdata, rowlimit=None)
+    ds.finalise()
+
+def load(options):
+    soom.setpath(options.path)
+    soom.writepath = soom.searchpath[0]
+    httptimings = make_httptimings(options)
+    load_httptimings(httptimings, options)
+    httptimings.derivedcolumn(dername='start_hour',
+                   dercols=('started',), derfunc=der_dt,
+                   datatype='datetime', coltype='ordinal', label='Start hour')
+    httptimings.save()
+    # httptimings = make_httptimings(dsname='HTTPvariola',dslabel="PHU http round-trip timings (internet)")
+    # load_httptimings(httptimings,'variola_http.csv')
+    # httptimings.derivedcolumn(dername='start_hour',
+    #               dercols=('started',), derfunc=der_dt,
+    #               datatype='datetime', coltype='ordinal', label='Start hour')
+    # httptimings.save()
+
+
+if __name__ == '__main__':
+    optp = OptionParser()
+    optp.add_option('-P', '--path',
+                    type='string', dest='path', default=None,
+                    help='SOOM dataset path')
+    optp.add_option('--database',
+                    type='string', dest='database', 
+                    default='httpinteract', help='database name')
+    optp.add_option('--table',
+                    type='string', dest='table', 
+                    default='reports', help='database table')
+    optp.add_option('--dsname',
+                    type='string', dest='dsname', 
+                    default='httpinteract', help='SOOM dataset name')
+    optp.add_option('--dslabel',
+                    type='string', dest='dslabel', 
+                    default='http round-trip timings', 
+                    help='SOOM dataset label')
+    optp.add_option('--sitemap',
+                    type='string', dest='sitemap', 
+                    help='CSV file mapping site names')
+    optp.add_option('-v', '--verbose',
+                    action='store_true', dest='verbose', default=False,
+                    help='Enable vebosity')
+    options, args = optp.parse_args()
+    if args:
+        optp.error('Use --help for usage info')
+    load(options)
diff --git a/httpinteract/index.html b/httpinteract/index.html
new file mode 100644
index 0000000..61dfe18
--- /dev/null
+++ b/httpinteract/index.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Case Manager". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+
+    Copyright (C) 2006 Health Administration Corporation.
+    All Rights Reserved.
+
+-->
+<html>
+ <head>
+  <title>NetEpi web interactivity tester</title>
+  <link href="httpinteract.css" rel="stylesheet" type="text/css" />
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="httpinteract.js"></script>
+ </head>
+ <body>
+  <form id="params_form">
+   <h1>NetEpi web interactivity tester</h1>
+   <div id="motd"></div>
+   <div class="controls">
+    <input type="button" name="togopr" value="Start" onClick="stopStartTester(this);" />
+    <input type="button" name="toglog" value="View Log" onClick="toggleLog('log', this);" />
+   </div>
+   <div class="params">
+    <div class="prm">
+     <label for="siteinfo">Site info</label>
+     <input id="siteinfo" name="siteinfo">
+    </div>
+    <div class="prm">
+     <label for="server">Server</label>
+     <input id="server" name="server" value="/cgi-bin/httpinteract.cgi" readonly>
+    </div>
+    <div class="prm">
+     <label for=interval">Interval</label>
+     <input id="interval" name=interval" value="0.1" readonly> (minutes)
+    </div>
+    <div class="prm">
+     <label for="sendcount">Send byte count</label>
+     <input id="sendcount" name="sendcount" value="4096" readonly>
+    </div>
+   </div>
+   <table id="stats">
+    <tr><td>Running?</td><td id="stat_running"></td></tr>
+    <tr><td>Requests:</td><td id="stat_requests"></td></tr>
+    <tr><td>Responses:</td><td id="stat_responses"></td></tr>
+    <tr><td>Success rate:</td><td id="stat_rate"></td></tr>
+    <tr><td>Best:</td><td id="stat_minimum"></td></tr>
+    <tr><td>Worst:</td><td id="stat_maximum"></td></tr>
+    <tr><td>Average:</td><td id="stat_average"></td></tr>
+   </table>
+   <div id="results">
+    <noscript><div class="error">This system requires Javascript</div></noscript>
+   </div>
+  </form>
+  <div id="log" class="hidden">
+   <h2>Log</h2>
+   <div id="_MochiKit_LoggingPane">
+   </div>
+  </div>
+ </body>
+</html>
diff --git a/httpinteract/schema b/httpinteract/schema
new file mode 100755
index 0000000..6c43831
--- /dev/null
+++ b/httpinteract/schema
@@ -0,0 +1,20 @@
+webuser=www-data
+db=httpinteract
+table=reports
+
+createdb "${db}"
+psql "${db}" << EOF
+CREATE TABLE ${table} (
+    started TIMESTAMP,
+    siteinfo VARCHAR,
+    status VARCHAR,
+    elapsed FLOAT,
+    received INTEGER,
+    srcaddr INET,
+    forwarded VARCHAR,
+    useragent VARCHAR
+);
+CREATE INDEX timesite ON ${table} (started, siteinfo);
+GRANT SELECT, INSERT, UPDATE ON ${table} TO "${webuser}";
+EOF
+
diff --git a/httpinteract/sitemap_tim.csv b/httpinteract/sitemap_tim.csv
new file mode 100644
index 0000000..9a8ff79
--- /dev/null
+++ b/httpinteract/sitemap_tim.csv
@@ -0,0 +1,35 @@
+DoH @ Nth Sydney IE6 via SSL,DoH Nth Sydney via SSL
+Far West,Far West PHU
+GOULBURN,Goulburn PHU
+Gosford,Gosford PHU
+Greater Western Pubblic Health Unit - Bathurst Office,Bathurst PHU
+Hunter,Hunter PHU
+Hunter PHU,Hunter PHU
+North Coast,Lismore PHU
+North Coast Public Health Unit ( Lismore),Lismore PHU
+North Coast Public Health Unit (Lismore),Lismore PHU
+North Coast Public health Unit (Lismore),Lismore PHU
+North Coast public Health Unit ( Lismore),Lismore PHU
+North Coast public Health Unit (Lismore),Lismore PHU
+Northern Sydney PHU,Hornsby PHU
+Northern Sydney Public Health Unit,Hornsby PHU
+PHU - Dubbo,Dubbo PHU
+SESI - Wollongong,Illawarra PHU
+SESIAHS - Northern,Randwick PHU
+SSW Liverpool,Liverpool PHU
+SWAHS-Cumberland,Parramatta PHU
+SWAHS-Penrith,Penrith PHU
+Tamworth,Tamworth PHU
+Tim Churches office - head office,DoH Nth Sydney
+"Tim Churches' office, Nth Syd",DoH Nth Sydney
+"Tim Churches' office, Nth Sydney",DoH Nth Sydney
+"Tim Churches's office, Miller St, Nth Sydney",DoH Nth Sydney
+albury,Albury PHU
+camperdown,Camperdown PHU
+former GMA Albury,Albury PHU
+gosford,Gosford PHU
+justice health,Justice Health PHU
+lllawarra,Illawarra PHU
+northcoast area health service port macquarie,Port Macquarie PHU
+sesiphu - northern office,Randwick PHU
+tamworth,Tamworth PHU
diff --git a/images/add-input.png b/images/add-input.png
new file mode 100644
index 0000000..c336913
Binary files /dev/null and b/images/add-input.png differ
diff --git a/images/add-paste.png b/images/add-paste.png
new file mode 100644
index 0000000..62e8783
Binary files /dev/null and b/images/add-paste.png differ
diff --git a/images/add-question.png b/images/add-question.png
new file mode 100644
index 0000000..3ab27f9
Binary files /dev/null and b/images/add-question.png differ
diff --git a/images/add-s.png b/images/add-s.png
new file mode 100644
index 0000000..cc14866
Binary files /dev/null and b/images/add-s.png differ
diff --git a/images/add-s.xcf b/images/add-s.xcf
new file mode 100644
index 0000000..38893bb
Binary files /dev/null and b/images/add-s.xcf differ
diff --git a/images/add-section.png b/images/add-section.png
new file mode 100644
index 0000000..7a7386d
Binary files /dev/null and b/images/add-section.png differ
diff --git a/images/add-subsec.png b/images/add-subsec.png
new file mode 100644
index 0000000..5f2f7d9
Binary files /dev/null and b/images/add-subsec.png differ
diff --git a/images/add.xcf b/images/add.xcf
new file mode 100644
index 0000000..ad9359e
Binary files /dev/null and b/images/add.xcf differ
diff --git a/images/arrow-l.png b/images/arrow-l.png
new file mode 100644
index 0000000..5794c71
Binary files /dev/null and b/images/arrow-l.png differ
diff --git a/images/arrow-r.png b/images/arrow-r.png
new file mode 100644
index 0000000..b025ecb
Binary files /dev/null and b/images/arrow-r.png differ
diff --git a/images/arrow.xcf b/images/arrow.xcf
new file mode 100644
index 0000000..fb5a07c
Binary files /dev/null and b/images/arrow.xcf differ
diff --git a/images/box.png b/images/box.png
new file mode 100644
index 0000000..6d4a79d
Binary files /dev/null and b/images/box.png differ
diff --git a/images/button-add.png b/images/button-add.png
new file mode 100644
index 0000000..230e24a
Binary files /dev/null and b/images/button-add.png differ
diff --git a/images/button-del-nil.png b/images/button-del-nil.png
new file mode 100644
index 0000000..7656d5f
Binary files /dev/null and b/images/button-del-nil.png differ
diff --git a/images/button-del.png b/images/button-del.png
new file mode 100644
index 0000000..51bdd42
Binary files /dev/null and b/images/button-del.png differ
diff --git a/images/button-down.png b/images/button-down.png
new file mode 100644
index 0000000..0d7b0b9
Binary files /dev/null and b/images/button-down.png differ
diff --git a/images/button-edit-d.png b/images/button-edit-d.png
new file mode 100644
index 0000000..f044aec
Binary files /dev/null and b/images/button-edit-d.png differ
diff --git a/images/button-edit-l.png b/images/button-edit-l.png
new file mode 100644
index 0000000..9e1934c
Binary files /dev/null and b/images/button-edit-l.png differ
diff --git a/images/button-edit.png b/images/button-edit.png
new file mode 100644
index 0000000..3b0cd36
Binary files /dev/null and b/images/button-edit.png differ
diff --git a/images/button-nil.png b/images/button-nil.png
new file mode 100644
index 0000000..50ff4eb
Binary files /dev/null and b/images/button-nil.png differ
diff --git a/images/button-up.png b/images/button-up.png
new file mode 100644
index 0000000..550250c
Binary files /dev/null and b/images/button-up.png differ
diff --git a/images/close.png b/images/close.png
new file mode 100644
index 0000000..5f72a70
Binary files /dev/null and b/images/close.png differ
diff --git a/images/deletedbg.png b/images/deletedbg.png
new file mode 100644
index 0000000..9440ca0
Binary files /dev/null and b/images/deletedbg.png differ
diff --git a/images/favicon.ico b/images/favicon.ico
new file mode 100644
index 0000000..840f4f5
Binary files /dev/null and b/images/favicon.ico differ
diff --git a/images/favicon.xcf b/images/favicon.xcf
new file mode 100644
index 0000000..d6e7d17
Binary files /dev/null and b/images/favicon.xcf differ
diff --git a/images/help-g.png b/images/help-g.png
new file mode 100644
index 0000000..a1e8efc
Binary files /dev/null and b/images/help-g.png differ
diff --git a/images/help-w.png b/images/help-w.png
new file mode 100644
index 0000000..e8d5ed5
Binary files /dev/null and b/images/help-w.png differ
diff --git a/images/help.xcf b/images/help.xcf
new file mode 100644
index 0000000..edfc236
Binary files /dev/null and b/images/help.xcf differ
diff --git a/images/info.png b/images/info.png
new file mode 100644
index 0000000..d14ec42
Binary files /dev/null and b/images/info.png differ
diff --git a/images/netepi-1.xcf b/images/netepi-1.xcf
new file mode 100644
index 0000000..d279a55
Binary files /dev/null and b/images/netepi-1.xcf differ
diff --git a/images/netepi-2.xcf b/images/netepi-2.xcf
new file mode 100644
index 0000000..a9e343a
Binary files /dev/null and b/images/netepi-2.xcf differ
diff --git a/images/netepi-3.xcf b/images/netepi-3.xcf
new file mode 100644
index 0000000..a44b847
Binary files /dev/null and b/images/netepi-3.xcf differ
diff --git a/images/netepi-bb.png b/images/netepi-bb.png
new file mode 100644
index 0000000..c4833d7
Binary files /dev/null and b/images/netepi-bb.png differ
diff --git a/images/netepi.png b/images/netepi.png
new file mode 100644
index 0000000..8b1ee4c
Binary files /dev/null and b/images/netepi.png differ
diff --git a/images/plus-line.png b/images/plus-line.png
new file mode 100644
index 0000000..81087de
Binary files /dev/null and b/images/plus-line.png differ
diff --git a/images/plus.png b/images/plus.png
new file mode 100644
index 0000000..69acfad
Binary files /dev/null and b/images/plus.png differ
diff --git a/images/round-s.xcf b/images/round-s.xcf
new file mode 100644
index 0000000..125c6a7
Binary files /dev/null and b/images/round-s.xcf differ
diff --git a/images/round.xcf b/images/round.xcf
new file mode 100644
index 0000000..4afb9ce
Binary files /dev/null and b/images/round.xcf differ
diff --git a/images/showbull.png b/images/showbull.png
new file mode 100644
index 0000000..4beef6c
Binary files /dev/null and b/images/showbull.png differ
diff --git a/images/showbull.xcf b/images/showbull.xcf
new file mode 100644
index 0000000..33ea7c2
Binary files /dev/null and b/images/showbull.xcf differ
diff --git a/images/small-dot.xcf b/images/small-dot.xcf
new file mode 100644
index 0000000..ebff3a1
Binary files /dev/null and b/images/small-dot.xcf differ
diff --git a/images/sortarrow.xcf b/images/sortarrow.xcf
new file mode 100644
index 0000000..ec1108e
Binary files /dev/null and b/images/sortarrow.xcf differ
diff --git a/images/sortdn.png b/images/sortdn.png
new file mode 100644
index 0000000..b5755a3
Binary files /dev/null and b/images/sortdn.png differ
diff --git a/images/sortdnsel.png b/images/sortdnsel.png
new file mode 100644
index 0000000..56c7521
Binary files /dev/null and b/images/sortdnsel.png differ
diff --git a/images/sortup.png b/images/sortup.png
new file mode 100644
index 0000000..6e578e5
Binary files /dev/null and b/images/sortup.png differ
diff --git a/images/sortupsel.png b/images/sortupsel.png
new file mode 100644
index 0000000..b2b398d
Binary files /dev/null and b/images/sortupsel.png differ
diff --git a/images/title-gradient.png b/images/title-gradient.png
new file mode 100644
index 0000000..7cea6b1
Binary files /dev/null and b/images/title-gradient.png differ
diff --git a/install.py b/install.py
new file mode 100644
index 0000000..21e2be7
--- /dev/null
+++ b/install.py
@@ -0,0 +1,149 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+sys.path.insert(0, os.path.normpath(os.path.dirname(__file__)))
+from simpleinst import *
+
+# The simpleinst "config" object maintains a layered view of configuration
+# sources. On attribute access, the layers are searched for the named
+# attribute, and the topmost match returned.
+#
+# The layers are:
+#
+#    10. command line
+#    20. config.py (distibution defaults, local customisation)
+#    30. install.py (includes any explicitly set config attributes)
+#    40. platform defaults (from simpleinst.platform)
+#    50. defaults (from simpleinst.defaults)
+#
+# We add an import of the cgi_target config.py (run-time config) at priority 15
+# so that previous configuration directives are honoured.
+
+assert config.appname == os.path.basename(config.appname),\
+    "appname must not contain pathname components"
+
+# Installer derived configuration (essentially defaults):
+config.cgi_target = joinpath(config.cgi_dir, config.appname)
+config.html_target = joinpath(config.html_dir, config.appname)
+config.scratchdir = os.path.join(config.html_target, 'scratch')
+
+config.install_owner = 'root'
+config.session_secret = secret()
+config.registration_notify = ''
+config.exception_notify = ''
+config.dsn = '::%s:' % config.appname
+config.install_logo = ''
+config.install_logo_small = ''
+
+# If this is a developer build, update SVN revision:
+svnrev = collect('svnversion %s 2> /dev/null' % config.base_dir)
+if svnrev and svnrev != 'exported':
+    f = open(os.path.join(config.base_dir, 'casemgr', 'svnrev.py'), 'w')
+    f.write('__svnrev__ = %r\n' % svnrev)
+    f.close()
+
+# Load any existing run-time config file
+config.source_file(15, 'config', config.cgi_target,
+                   exclude=['cgi_target', 'html_target', 'scratchdir'])
+
+# Write run-time config, excluding installer-specific vars
+config_exclude = [
+    'base_dir',
+    'bin_dir',
+    'cgi_dir',
+    'compile_py',
+    'create_db',
+    'html_dir',
+    'install_*',
+    'platform',
+    'python',
+    'web_user',
+] 
+
+config_owner = '%s:%s' % (config.install_owner, 
+                          user_lookup(config.web_user)[1])
+config.write_file(joinpath(config.cgi_target, 'config.py'),
+                  exclude=config_exclude, 
+                  owner=config_owner, 
+                  mode=0640)
+
+# Configure install hooks - delete (stale?) pyc files, optionally compile
+on_install('*.py', rm_pyc)
+if config.compile_py:
+    on_install('*.py', py_compile)
+
+appname_filter = Filter(config, pattern=r'{{APPNAME}}',
+                            subst=config.appname)
+
+# Sundry static content
+install(target = config.html_target, 
+        filter = appname_filter,
+        base = 'app', files = ['*.css', '*.js', '*.html',
+                                   'lang/*.js', 'help/*.html'])
+
+images = joinpath(config.html_target, 'images')
+image_exclude = []
+if config.install_logo:
+    image_exclude.append('netepi-bb.png')
+    copy(config.install_logo, joinpath(images, 'netepi-bb.png'), mode=0755)
+if config.install_logo_small:
+    image_exclude.append('netepi.png')
+    copy(config.install_logo_small, joinpath(images, 'netepi.png'), mode=0755)
+install(target = images, base = 'images', files = ['*.png', '*.ico'],
+        exclude = image_exclude)
+
+make_dirs(joinpath(config.html_target, 'scratch'), config.web_user)
+
+
+# Applications
+install(target = config.cgi_target, 
+        filter = python_bang_path_filter,
+        base = 'app', files = ['app.py', 'menu.py'], mode = 0755)
+
+# Command line tool
+cgitarget_filter = Filter(config, pattern=r'{{CGITARGET}}',
+                            subst=config.cgi_target)
+
+copy('app/cmdline.py', joinpath(config.bin_dir, 'netepi-' + config.appname),
+        filter=[python_bang_path_filter, cgitarget_filter],
+        mode = 0755)
+
+# App modules and pages
+install(target = config.cgi_target, 
+        base = '.', files = ['pages'], 
+        include = ['*.py', '*.html'])
+
+# libraries
+install(target = config.cgi_target, 
+        base = '.', files = ['cocklebur', 'casemgr', 'wiki'], include='*.py')
+
+# e-mail templates
+install(target = config.cgi_target, 
+        base = '.', files = ['mail'])
+
+# Compile database describer, create form tables if need be
+if config.create_db:
+    py_installer('tools/compile_db.py')
+
+print """\
+*******************************************************************************
+Reminder - if you are using a deployment scheme that utilises a persistent
+application, such as mod_fastcgi or mod_python, you will now need to
+restart the application (how this is performed depends on the scheme -
+if running under apache, reloading it may suffice).
+"""
diff --git a/labsurv/LICENCE b/labsurv/LICENCE
new file mode 100644
index 0000000..15c9da4
--- /dev/null
+++ b/labsurv/LICENCE
@@ -0,0 +1,548 @@
+COPYRIGHT AND LICENSING ARRANGEMENTS
+====================================
+
+All material is copyright 2004-2010 Health Administration Corporation
+(New South Wales Department of Health) and others, except where
+otherwise indicated in the header section of the specific files listed
+below.
+
+NetEpi is licensed under the terms of the Health Administration
+Corporation Open Source Licence V1.2 (HACOS Licence V1.2), the complete
+text of which appears below.
+
+-----------------------------------------------------------------
+
+HEALTH ADMINISTRATION CORPORATION OPEN SOURCE LICENCE VERSION 1.2
+=================================================================
+
+1. DEFINITIONS. 
+
+   "Commercial Use" shall mean distribution or otherwise making the
+   Covered Software available to a third party.
+
+   "Contributor" shall mean each entity that creates or contributes to
+   the creation of Modifications.
+
+   "Contributor Version" shall mean in case of any Contributor the
+   combination of the Original Software, prior Modifications used by a
+   Contributor, and the Modifications made by that particular Contributor
+   and in case of Health Administration Corporation in addition the
+   Original Software in any form, including the form as Executable.
+
+   "Covered Software" shall mean the Original Software or Modifications
+   or the combination of the Original Software and Modifications, in
+   each case including portions thereof.
+
+   "Electronic Distribution Mechanism" shall mean a mechanism generally
+   accepted in the software development community for the electronic
+   transfer of data.
+
+   "Executable" shall mean Covered Software in any form other than
+   Source Code.
+
+   "Initial Developer" shall mean the individual or entity identified as
+   the Initial Developer in the Source Code notice required by Exhibit A.
+
+   "Health Administration Corporation" shall mean the Health
+   Administration Corporation as established by the Health Administration
+   Act 1982, as amended, of the State of New South Wales, Australia. The
+   Health Administration Corporation has its offices at 73 Miller Street,
+   North Sydney, New South Wales 2059, Australia.
+
+   "Larger Work" shall mean a work, which combines Covered Software or
+   portions thereof with code not governed by the terms of this Licence.
+
+   "Licence" shall mean this document.
+
+   "Licensable" shall mean having the right to grant, to the maximum
+   extent possible, whether at the time of the initial grant or
+   subsequently acquired, any and all of the rights conveyed herein.
+
+   "Modifications" shall mean any addition to or deletion from the
+   substance or structure of either the Original Software or any previous
+   Modifications. When Covered Software is released as a series of files,
+   a Modification is:
+
+   a) Any addition to or deletion from the contents of a file
+      containing Original Software or previous Modifications.
+
+   b) Any new file that contains any part of the Original Software or
+      previous Modifications.
+
+   "Original Software" shall mean the Source Code of computer software
+   code which is described in the Source Code notice required by Exhibit
+   A as Original Software, and which, at the time of its release under
+   this Licence is not already Covered Software governed by this Licence.
+
+   "Patent Claims" shall mean any patent claim(s), now owned or hereafter
+   acquired, including without limitation, method, process, and apparatus
+   claims, in any patent Licensable by grantor.
+
+   "Source Code" shall mean the preferred form of the Covered Software
+   for making modifications to it, including all modules it contains,
+   plus any associated interface definition files, scripts used to
+   control compilation and installation of an Executable, or source
+   code differential comparisons against either the Original Software or
+   another well known, available Covered Software of the Contributor's
+   choice. The Source Code can be in a compressed or archival form,
+   provided the appropriate decompression or de-archiving software is
+   widely available for no charge.
+
+   "You" (or "Your") shall mean an individual or a legal entity exercising
+   rights under, and complying with all of the terms of, this Licence or
+   a future version of this Licence issued under Section 6.1. For legal
+   entities, "You" includes an entity which controls, is controlled
+   by, or is under common control with You. For the purposes of this
+   definition, "control" means (a) the power, direct or indirect,
+   to cause the direction or management of such entity, whether by
+   contract or otherwise, or (b) ownership of more than fifty per cent
+   (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. SOURCE CODE LICENCE. 
+
+2.1 Health Administration Corporation Grant. 
+
+Subject to the terms of this Licence, Health Administration Corporation
+hereby grants You a world-wide, royalty-free, non-exclusive licence,
+subject to third party intellectual property claims:
+
+a) under copyrights Licensable by Health Administration Corporation
+   to use, reproduce, modify, display, perform, sublicense and
+   distribute the Original Software (or portions thereof) with or without
+   Modifications, and/or as part of a Larger Work;
+
+b) and under Patents Claims infringed by the making, using or selling
+   of Original Software, to make, have made, use, practice, sell, and
+   offer for sale, and/or otherwise dispose of the Original Software
+   (or portions thereof).
+
+c) The licences granted in this Section 2.1(a) and (b) are effective
+   on the date Health Administration Corporation first distributes
+   Original Software under the terms of this Licence.
+
+d) Notwithstanding Section 2.1(b) above, no patent licence is granted:
+   1) for code that You delete from the Original Software; 2) separate
+   from the Original Software; or 3) for infringements caused by: i)
+   the modification of the Original Software or ii) the combination of
+   the Original Software with other software or devices.
+
+2.2 Contributor Grant. 
+
+Subject to the terms of this Licence and subject to third party
+intellectual property claims, each Contributor hereby grants You a
+world-wide, royalty-free, non-exclusive licence:
+
+a) under copyrights Licensable by Contributor, to use, reproduce,
+   modify, display, perform, sublicense and distribute the Modifications
+   created by such Contributor (or portions thereof) either on an
+   unmodified basis, with other Modifications, as Covered Software and/or
+   as part of a Larger Work; and
+
+b) under Patent Claims necessarily infringed by the making, using,
+   or selling of Modifications made by that Contributor either alone
+   and/or in combination with its Contributor Version (or portions of
+   such combination), to make, use, sell, offer for sale, have made,
+   and/or otherwise dispose of: 1) Modifications made by that Contributor
+   (or portions thereof); and 2) the combination of Modifications made
+   by that Contributor with its Contributor Version (or portions of
+   such combination).
+
+c) The licences granted in Sections 2.2(a) and 2.2(b) are effective
+   on the date Contributor first makes Commercial Use of the Covered
+   Software.
+
+d) Notwithstanding Section 2.2(b) above, no patent licence is granted:
+   1) for any code that Contributor has deleted from the Contributor
+   Version; 2) separate from the Contributor Version; 3) for infringements
+   caused by: i) third party modifications of Contributor Version or ii)
+   the combination of Modifications made by that Contributor with other
+   software (except as part of the Contributor Version) or other devices;
+   or 4) under Patent Claims infringed by Covered Software in the absence
+   of Modifications made by that Contributor.
+
+3. DISTRIBUTION OBLIGATIONS. 
+
+3.1 Application of Licence. 
+
+The Modifications which You create or to which You contribute are governed
+by the terms of this Licence, including without limitation Section
+2.2. The Source Code version of Covered Software may be distributed
+only under the terms of this Licence or a future version of this Licence
+released under Section 6.1, and You must include a copy of this Licence
+with every copy of the Source Code You distribute. You may not offer or
+impose any terms on any Source Code version that alters or restricts the
+applicable version of this Licence or the recipients' rights hereunder.
+
+3.2 Availability of Source Code. 
+
+Any Modification which You create or to which You contribute must be made
+available in Source Code form under the terms of this Licence either on
+the same media as an Executable version or via an accepted Electronic
+Distribution Mechanism to anyone to whom you made an Executable version
+available; and if made available via Electronic Distribution Mechanism,
+must remain available for at least twelve (12) months after the date it
+initially became available, or at least six (6) months after a subsequent
+version of that particular Modification has been made available to
+such recipients. You are responsible for ensuring that the Source Code
+version remains available even if the Electronic Distribution Mechanism
+is maintained by a third party.
+
+3.3 Description of Modifications. 
+
+You must cause all Covered Software to which You contribute to contain
+a file documenting the changes You made to create that Covered Software
+and the date of any change. You must include a prominent statement that
+the Modification is derived, directly or indirectly, from Original
+Software provided by Health Administration Corporation and including
+the name of Health Administration Corporation in (a) the Source Code,
+and (b) in any notice in an Executable version or related documentation
+in which You describe the origin or ownership of the Covered Software.
+
+3.4 Intellectual Property Matters 
+
+a) Third Party Claims. 
+
+   If Contributor has knowledge that a licence under a third party's
+   intellectual property rights is required to exercise the rights
+   granted by such Contributor under Sections 2.1 or 2.2, Contributor
+   must include a text file with the Source Code distribution titled
+   "LEGAL'' which describes the claim and the party making the claim
+   in sufficient detail that a recipient will know whom to contact. If
+   Contributor obtains such knowledge after the Modification is made
+   available as described in Section 3.2, Contributor shall promptly
+   modify the LEGAL file in all copies Contributor makes available
+   thereafter and shall take other steps (such as notifying appropriate
+   mailing lists or newsgroups) reasonably calculated to inform those
+   who received the Covered Software that new knowledge has been obtained.
+
+b) Contributor APIs. 
+
+   If Contributor's Modifications include an application programming
+   interface (API) and Contributor has knowledge of patent licences
+   which are reasonably necessary to implement that API, Contributor
+   must also include this information in the LEGAL file.
+
+c) Representations. 
+
+   Contributor represents that, except as disclosed pursuant to Section
+   3.4(a) above, Contributor believes that Contributor's Modifications are
+   Contributor's original creation(s) and/or Contributor has sufficient
+   rights to grant the rights conveyed by this Licence.
+
+3.5 Required Notices. 
+
+You must duplicate the notice in Exhibit A in each file of the Source
+Code. If it is not possible to put such notice in a particular Source
+Code file due to its structure, then You must include such notice in a
+location (such as a relevant directory) where a user would be likely to
+look for such a notice. If You created one or more Modification(s) You
+may add your name as a Contributor to the notice described in Exhibit
+A. You must also duplicate this Licence in any documentation for the
+Source Code where You describe recipients' rights or ownership rights
+relating to Covered Software. You may choose to offer, and to charge a
+fee for, warranty, support, indemnity or liability obligations to one or
+more recipients of Covered Software. However, You may do so only on Your
+own behalf, and not on behalf of Health Administration Corporation or any
+Contributor. You must make it absolutely clear that any such warranty,
+support, indemnity or liability obligation is offered by You alone,
+and You hereby agree to indemnify Health Administration Corporation and
+every Contributor for any liability incurred by Health Administration
+Corporation or such Contributor as a result of warranty, support,
+indemnity or liability terms You offer.
+
+3.6 Distribution of Executable Versions. 
+
+You may distribute Covered Software in Executable form only if the
+requirements of Sections 3.1-3.5 have been met for that Covered Software,
+and if You include a notice stating that the Source Code version of the
+Covered Software is available under the terms of this Licence, including
+a description of how and where You have fulfilled the obligations of
+Section 3.2. The notice must be conspicuously included in any notice in
+an Executable version, related documentation or collateral in which You
+describe recipients' rights relating to the Covered Software. You may
+distribute the Executable version of Covered Software or ownership rights
+under a licence of Your choice, which may contain terms different from
+this Licence, provided that You are in compliance with the terms of this
+Licence and that the licence for the Executable version does not attempt
+to limit or alter the recipient's rights in the Source Code version from
+the rights set forth in this Licence. If You distribute the Executable
+version under a different licence You must make it absolutely clear
+that any terms which differ from this Licence are offered by You alone,
+not by Health Administration Corporation or any Contributor. You hereby
+agree to indemnify Health Administration Corporation and every Contributor
+for any liability incurred by Health Administration Corporation or such
+Contributor as a result of any such terms You offer.
+
+3.7 Larger Works. 
+
+You may create a Larger Work by combining Covered Software with other
+software not governed by the terms of this Licence and distribute the
+Larger Work as a single product. In such a case, You must make sure the
+requirements of this Licence are fulfilled for the Covered Software.
+
+4. INABILITY TO COMPLY DUE TO STATUTE OR REGULATION. 
+
+If it is impossible for You to comply with any of the terms of this
+Licence with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with the
+terms of this Licence to the maximum extent possible; and (b) describe the
+limitations and the code they affect. Such description must be included
+in the LEGAL file described in Section 3.4 and must be included with all
+distributions of the Source Code.  Except to the extent prohibited by
+statute or regulation, such description must be sufficiently detailed
+for a recipient of ordinary skill to be able to understand it.
+
+5. APPLICATION OF THIS LICENCE. 
+
+This Licence applies to code to which Health Administration Corporation
+has attached the notice in Exhibit A and to related Covered Software.
+
+6. VERSIONS OF THE LICENCE. 
+
+6.1 New Versions. 
+
+Health Administration Corporation may publish revised and/or new
+versions of the Licence from time to time. Each version will be given
+a distinguishing version number.
+
+6.2 Effect of New Versions. 
+
+Once Covered Software has been published under a particular version
+of the Licence, You may always continue to use it under the terms of
+that version. You may also choose to use such Covered Software under
+the terms of any subsequent version of the Licence published by Health
+Administration Corporation. No one other than Health Administration
+Corporation has the right to modify the terms applicable to Covered
+Software created under this Licence.
+
+7. DISCLAIMER OF WARRANTY. 
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENCE ON AN "AS IS'' BASIS,
+WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF
+DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS
+WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU
+(NOT HEALTH ADMINISTRATION CORPORATION, ITS LICENSORS OR AFFILIATES OR
+ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR
+OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
+OF THIS LICENCE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER
+EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION. 
+
+8.1 This Licence and the rights granted hereunder will terminate
+automatically if You fail to comply with terms herein and fail to
+cure such breach within 30 days of becoming aware of the breach. All
+sublicences to the Covered Software which are properly granted shall
+survive any termination of this Licence. Provisions which, by their
+nature, must remain in effect beyond the termination of this Licence
+shall survive.
+
+8.2 If You initiate litigation by asserting a patent infringement claim
+(excluding declatory judgment actions) against Health Administration
+Corporation or a Contributor (Health Administration Corporation
+or Contributor against whom You file such action is referred to as
+"Participant") alleging that:
+
+a) such Participant's Contributor Version directly or indirectly
+   infringes any patent, then any and all rights granted by such
+   Participant to You under Sections 2.1 and/or 2.2 of this Licence
+   shall, upon 60 days notice from Participant terminate prospectively,
+   unless if within 60 days after receipt of notice You either: (i)
+   agree in writing to pay Participant a mutually agreeable reasonable
+   royalty for Your past and future use of Modifications made by such
+   Participant, or (ii) withdraw Your litigation claim with respect to
+   the Contributor Version against such Participant. If within 60 days
+   of notice, a reasonable royalty and payment arrangement are not
+   mutually agreed upon in writing by the parties or the litigation
+   claim is not withdrawn, the rights granted by Participant to
+   You under Sections 2.1 and/or 2.2 automatically terminate at the
+   expiration of the 60 day notice period specified above.
+
+b) any software, hardware, or device, other than such Participant's
+   Contributor Version, directly or indirectly infringes any patent,
+   then any rights granted to You by such Participant under Sections
+   2.1(b) and 2.2(b) are revoked effective as of the date You first
+   made, used, sold, distributed, or had made, Modifications made by
+   that Participant.
+
+8.3 If You assert a patent infringement claim against Participant
+alleging that such Participant's Contributor Version directly or
+indirectly infringes any patent where such claim is resolved (such as by
+licence or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licences granted by such
+Participant under Sections 2.1 or 2.2 shall be taken into account in
+determining the amount or value of any payment or license.  
+
+8.4 In the event of termination under Sections 8.1 or 8.2 above, all
+end user licence agreements (excluding distributors and resellers) which
+have been validly granted by You or any distributor hereunder prior to
+termination shall survive termination.
+
+9. LIMITATION OF LIABILITY. 
+
+9.1 UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, HEALTH
+ADMINISTRATION CORPORATION, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR
+OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE
+TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
+ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE
+BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, BUT MAY ALLOW
+LIABILITY TO BE LIMITED; IN SUCH CASES, A PARTY'S, ITS EMPLOYEES',
+LICENSORS' OR AFFILIATES' LIABILITY SHALL BE LIMITED TO AUD$100. NOTHING
+CONTAINED IN THIS LICENCE SHALL PREJUDICE THE STATUTORY RIGHTS OF ANY
+PARTY DEALING AS A CONSUMER.
+
+9.2 Notwithstanding any other clause in the licence, and to the extent
+permitted by law:
+
+(a) Health Administration Corporation ("the Corporation") excludes all
+    conditions and warranties which would otherwise be implied into
+    a supply of goods or services arising out of or in relation to
+    the granting of this licence by the Corporation or any associated
+    acquisition of software to which this licence relates;
+
+(b) Where a condition or warranty is implied into such a supply and
+    that condition or warranty cannot be excluded by law that warranty
+    or condition is implied into that supply and the liability of the
+    Health Administration Corporation for a breach of that condition or
+    warranty is limited to the fullest extent permitted by law and, in
+    respect of conditions and warranties implied by the Trade Practices
+    Act (Commonwealth of Australia) 1974, is limited, to the extent
+    permitted by law, to one or more of the following at the election
+    of the Corporation:
+
+    (A) In the case of goods: (i) the replacement of the goods or the
+        supply of equivalent goods; (ii) the repair of the goods; (iii)
+        the payment of the cost of replacing the goods or of acquiring
+        equivalent goods; (iv) the payment of the cost of having the
+        goods repaired; and
+
+    (B) in the case of services: (i) the supplying of the services again;
+        or (ii) the payment of the cost of having the services supplied
+        again.
+
+10. MISCELLANEOUS. 
+
+This Licence represents the complete agreement concerning subject matter
+hereof. All rights in the Covered Software not expressly granted under
+this Licence are reserved. Nothing in this Licence shall grant You any
+rights to use any of the trademarks of Health Administration Corporation
+or any of its Affiliates, even if any of such trademarks are included
+in any part of Covered Software and/or documentation to it.
+
+This Licence is governed by the laws of the State of New South Wales,
+Australia excluding its conflict-of-law provisions. All disputes or
+litigation arising from or relating to this Agreement shall be subject
+to the jurisdiction of the Supreme Court of New South Wales. If any part
+of this Agreement is found void and unenforceable, it will not affect
+the validity of the balance of the Agreement, which shall remain valid
+and enforceable according to its terms.
+
+11. RESPONSIBILITY FOR CLAIMS. 
+
+As between Health Administration Corporation and the Contributors,
+each party is responsible for claims and damages arising, directly or
+indirectly, out of its utilisation of rights under this Licence and You
+agree to work with Health Administration Corporation and Contributors
+to distribute such responsibility on an equitable basis. Nothing herein
+is intended or shall be deemed to constitute any admission of liability.
+
+EXHIBIT A 
+
+The contents of this file are subject to the HACOS Licence Version 1.2
+(the "Licence"); you may not use this file except in compliance with
+the Licence.
+
+Software distributed under the Licence is distributed on an "AS IS"
+basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+Licence for the specific language governing rights and limitations under
+the Licence.
+
+The Original Software is "NetEpi Collection". The Initial Developer
+of the Original Software is the Health Administration Corporation,
+incorporated in the State of New South Wales, Australia.
+
+Copyright (C) 2004-2011 Health Administration Corporation and others. 
+All Rights Reserved.
+Contributors: See the CONTRIBUTORS file for details of contributions.
+
+APPENDIX 1. DIFFERENCES BETWEEN THE HACOS LICENCE VERSION 1.2, THE
+MOZILLA PUBLIC LICENSE VERSION 1.1 AND THE NOKIA OPEN SOURCE LICENSE
+(NOKOS LICENSE) VERSION 1.0A
+
+The HACOS Licence Version 1.2 was derived from the Mozilla Public
+License Version 1.1 using some of the changes to the Mozilla Public
+License embodied in the Nokia Open Source License (NOKOS License)
+Version 1.0a. The differences between the HACOS Licence Version 1.2
+(this document), the Mozilla Public License and the NOKOS License are
+as follows:
+
+i.    The title of the licence was changed to "Health Administration
+      Corporation Open Source Licence Version 1.2".
+
+ii.   Globally, all references to "Netscape Communications Corporation",
+      "Mozilla", "Nokia" and "Nokia Corporation" were changed to "Health
+      Administration Corporation".
+
+iii.  Globally, the words "means", "Covered Code" and "Covered Software"
+      as used in the Mozilla Public License were changed to "shall means",
+      "Covered Code" and "Covered Software" respectively, as used in
+      the NOKOS License.
+
+iv.   In Section 1 (Definitions), a definition of "Health Administration
+      Corporation" was added.
+
+v.    In Section 2, the term "intellectual property rights" used in the
+      Mozilla Public License was replaced by the term "copyrights"
+      as used in the NOKOS License.
+
+vi.   In Section 2.2 (Contributor Grant), the words "Subject to the
+      terms of this License" which appear in the NOKOS License were
+      added to the Mozilla Public License.
+
+vii.  The sentence "However, You may include an additional document
+      offering the additional rights described in Section 3.5." which
+      appears in the Mozilla Public License was omitted.
+
+viii. Section 6.3 (Derivative Works) of the Mozilla Public License,
+      which permits modifications to the Mozilla Public License,
+      was omitted.
+
+ix.   The original Section 9 (Limitation of Liability) was renumbered
+      as Section 9.1, a maximum liability of AUD$100 was specified
+      for those jurisdictions which do not allow complete exclusion of
+      liability but which do allow limitation of liability. The sentence
+      "NOTHING CONTAINED IN THE LICENSE SHALL PREJUDICE THE STATUTORY
+      RIGHTS OF ANY PARTY DEALING AS A CONSUMER.", which appears in the
+      NOKOS License but not in the Mozilla Public License, was added.
+
+x.    Section 9.2 was added in order to further limit liability to the
+      maximum extent permitted by the Commonwealth of Australia Trade
+      Practices Act 1974.
+
+xi.   Section 10 of the Mozilla Public License, which provides additional
+      conditions for United States Government End Users, was omitted.
+
+xii.  The governing law and jurisdiction for the settlement of disputes
+      in Section 11 of the Mozilla Public License and Section 10 of the
+      NOKOS License was changed to the laws of the State of New South
+      Wales and the Supreme Court of New South Wales respectively. The
+      exclusion of the application of the United Nations Convention on
+      Contracts for the International Sale of Goods which appears in
+      the Mozilla Public License was omitted.
+
+xiii. Section 13 (Multiple-Licensed Code) of the Mozilla Public License 
+      was omitted.
+
+xiv.  The provisions for alternative licensing arrangement for contributed
+      code which appear in Exhibit A of the Mozilla Public License 
+      were omitted.
+
diff --git a/labsurv/README b/labsurv/README
new file mode 100644
index 0000000..cbefb1b
--- /dev/null
+++ b/labsurv/README
@@ -0,0 +1,46 @@
+Weekly respiratory virus laboratory surveillance application
+============================================================
+
+This is a small, bespoke web application to assist in the collection of
+weekly respiratory virus test data from participating laboratories in New
+South Wales, Australia. As such,the application has limited flexibility,
+and that which it does have requires editing of its python source files
+(in particular, the labsurv/labsurv.py file to change the list of
+labs, tests and diagnoses). However, we have included it in the NetEpi
+Collection distribution in order to demonstrate how the infrastructure
+used by NetEpi can also be used to rapidly create custom applications
+for specific purposes. The application could be used as a template for
+similar data collection use-cases (scenarios).
+
+It also serves as a prototype for the possible future inclusion in
+NetEpi of a more generalised facility to collect aggregate statistics
+(as it stands, NetEpi Collection is strongly oriented towards collection
+data on individual cases and contacts, not aggregate data).
+
+Prerequisites and configuration options are a subset of those offered by
+NetEpi Collection. Prerequisites are unix, apache, python, albatross,
+mx Tools, PostgreSQL, pyPgSQL or ocpgdb. Configuration options include
+appname, apptitle, dsn, cgi_dir, html_dir, and tracedb. Please refer to
+the NetEpi Collection README for more details.
+
+To create the database ("labsurv" and "www-data" can be changed to suit
+your environment):
+
+    createdb labsurv
+    psql labsurv -f schema/schema.sql
+    schema/chown www-data | psql labsurv
+
+
+LICENCE
+=======
+
+All materials associated with "NetEpi" are Copyright (C) 2004-2010 Health
+Administration Corporation (New South Wales Department of Health) and
+others (see the CONTRIBUTORS file for details).
+
+NetEpi is licensed under the terms of the Health Administration
+Corporation Open Source Licence Version 1.2 (HACOS Licence V1.2),
+the full text of which can be found in the LICENCE file provided with
+NetEpi Collection.
+
+
diff --git a/labsurv/app/app.py b/labsurv/app/app.py
new file mode 100644
index 0000000..bf81aa7
--- /dev/null
+++ b/labsurv/app/app.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard libraries
+import csv
+
+# 3rd party libs
+from albatross import SimpleApp, SimpleAppContext
+from albatross.fcgiapp import Request, running
+# mxTools
+from mx import DateTime
+
+# Application modules
+from labsurv import labsurv, pcode, dbapi
+import config
+
+dbapi.execute_debug(config.tracedb)
+
+class PageError(Exception): pass
+
+class PageBase:
+
+    def new_report(self, ctx):
+        user = ctx.request.get_param('REMOTE_USER')
+        ctx.locals.report = labsurv.LabSurv(user)
+
+    def page_display(self, ctx):
+        ctx.run_template(self.name + '.html')
+
+    def do_back(self, ctx, ignore):
+        ctx.pop_page()
+
+    def page_process(self, ctx):
+        for field_name in ctx.request.field_names():
+            # <input type="image"> returns fieldname.x and fieldname.y
+            if field_name.endswith('.x'):
+                field_name = field_name[:-2]
+            elif field_name.endswith('.y'):
+                continue
+            fields = field_name.split(':')
+            meth_name = fields.pop(0)
+            try:
+                meth = getattr(self, 'do_' + meth_name)
+            except AttributeError:
+                continue
+            if not fields:
+                value = getattr(ctx.locals, field_name, None)
+                if isinstance(value, list):
+                    for v in value:
+                        if v:
+                            value = v
+                            break
+                if value:
+                    fields = [value]
+            if fields:
+                try:
+                    if meth(ctx, *fields):
+                        return True
+                except (PageError, labsurv.ReportError), e:
+                    ctx.msg('err', str(e))
+        return False
+
+
+class WriteWrapper:
+    # The CSV module wants a file-like object, when it really
+    # just needs a write function - too late to fix it now!
+    def __init__(self, writer):
+        self.write = writer
+
+
+class DatePage(PageBase):
+
+    name = 'date'
+
+    def page_enter(self, ctx):
+        self.new_report(ctx)
+        ctx.add_session_vars('report')
+
+    def page_display(self, ctx):
+        export = getattr(ctx.locals, 'export', None)
+        if export:
+            filename = '%s_%s_%s.csv' % (config.appname, ctx.locals.mode,
+                                         DateTime.now().strftime('%Y%m%d-%H%M'))
+            ctx.set_save_session(False)
+            # IE will not download via SSL if caching is disabled. 
+            # See: http://support.microsoft.com/?kbid=323308
+            ctx.del_header('Cache-Control')
+            ctx.del_header('Pragma')
+            ctx.set_header('Content-Type', 'application/vnd.ms-excel')
+            ctx.set_header('Content-Disposition', 
+                           'attachment; filename="%s"' % filename)
+            writer = csv.writer(WriteWrapper(ctx.send_content))
+            writer.writerows(export)
+        else:
+            PageBase.page_display(self, ctx)
+
+    def do_next(self, ctx, ignore):
+        if ctx.locals.report.load():
+            ctx.msg('info', 'Loaded existing report')
+        ctx.push_page('totals')
+
+    def do_export(self, ctx, mode):
+        ctx.locals.mode = mode
+        ctx.locals.export = ctx.locals.report.export(mode)
+
+
+
+class TotalsPage(PageBase):
+
+    name = 'totals'
+
+    def do_back(self, ctx, ignore):
+        ctx.locals.report.update_totals()
+        ctx.pop_page()
+
+    def do_next(self, ctx, ignore):
+        ctx.locals.report.update_totals()
+        ctx.push_page('details')
+
+
+class DetailsPage(PageBase):
+
+    name = 'details'
+
+    def do_back(self, ctx, ignore):
+        ctx.locals.report.update_diags()
+        ctx.pop_page()
+
+    def do_next(self, ctx, ignore):
+        ctx.locals.report.update_diags()
+        ctx.push_page('cases')
+
+
+class CasesPage(PageBase):
+
+    name = 'cases'
+
+    def page_okay(self, ctx):
+        errors = 0
+        for case in ctx.locals.report.positive_case_page():
+            try:
+                case.check()
+            except labsurv.ReportError, e:
+                ctx.msg('err', 'Record %s: %s' % (case.idx + 1, e))
+                errors += 1
+        return not errors
+    
+    def do_back(self, ctx, ignore):
+        ctx.locals.report.update_report()       # Notes
+        if self.page_okay(ctx):
+            ctx.locals.report.update_positive_cases()
+            if not ctx.locals.report.prev_case_page():
+                ctx.pop_page()
+
+    def do_next(self, ctx, ignore):
+        ctx.locals.report.update_report()       # Notes
+        if self.page_okay(ctx):
+            ctx.locals.report.update_positive_cases()
+            if not ctx.locals.report.next_case_page():
+                ctx.push_page('submit')
+
+
+class SubmitPage(PageBase):
+
+    name = 'submit'
+
+    def do_submit(self, ctx, ignore):
+        ctx.locals.report.submit()
+        ctx.msg('info', 'Report submitted - Thank you.')
+        self.new_report(ctx)
+        ctx.pop_page('date')
+
+
+
+def ajax(req):
+    suburb = req.field_value('lookup_suburb')
+    suburb = suburb.upper().replace('.', '').replace('-', '')
+    if suburb.startswith('MT '):
+        suburb = 'MOUNT ' + suburb[3:]
+    req.write_header('Content-Type', 'text/html')
+    req.end_headers()
+    req.write_content('%r' % pcode.locality_to_postcode.get(suburb, ''))
+    req.return_code()   # Defacto "end of request"
+
+
+class AppContext(SimpleAppContext):
+    
+    def appath(self, *args):
+        return '/'.join(('', self.locals.appname) + args)
+
+    def msg(self, lvl, msg):
+        self.locals.msgs.append((lvl, msg))
+
+
+class App(SimpleApp):
+    pages = DatePage, TotalsPage, DetailsPage, CasesPage, SubmitPage
+
+    def __init__(self):
+        SimpleApp.__init__(self,
+                           base_url='app.py',
+                           template_path='pages',
+                           start_page='date',
+                           secret=config.session_secret)
+        for page_class in self.pages:
+            self.register_page(page_class.name, page_class())
+
+    def create_context(self):
+        ctx = AppContext(self)
+        for a, v in config.__dict__.iteritems():
+            setattr(ctx.locals, a, v)
+        ctx.locals.msgs = []
+        ctx.locals.appath = ctx.appath
+        ctx.run_template_once('macros.html')
+        return ctx
+
+
+app = App()
+
+
+if __name__ == '__main__':
+    while running():
+        req = Request()
+        if req.has_field('lookup_suburb'):
+            ajax(req)
+        else:
+            app.run(req)
+        dbapi.db.rollback()
+
diff --git a/labsurv/app/help.html b/labsurv/app/help.html
new file mode 100644
index 0000000..7f156ee
--- /dev/null
+++ b/labsurv/app/help.html
@@ -0,0 +1,133 @@
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <style type="text/css" media="all">
+   <!-- @import "style.css"; -->
+  </style>
+  <title>NSW Health weekly respiratory virus laboratory surveillance</title>
+ </head>
+ <body>
+  <div class="banner">
+   <img src="images/banner.jpg" border="0" alt="" />
+   <h1>NSW Health weekly respiratory virus laboratory surveillance</h1>
+   <br clear="both">
+  </div>
+  <div class="content">
+
+<h2>Introduction</h2>
+
+For further information about or assistance with this web-based facility,
+please contact the Senior Surveillance Officer - Respiratory Diseases in
+the Communicable Diseases Branch (CDB) of NSW Department of Health.<p>
+
+This facility replaces the previous paper- and fax-based system of
+voluntary reporting of respiratory virus laboratory test results to
+the Communicable Diseases Branch (CDB) of NSW Department of Health. During the winter
+surveillance period (start and end dates of which will be advised by CDB), participating
+laboratories should submit the requested information, on a weekly basis,
+as soon as possible after the close of each week. For the purposes of this data
+collection, weeks end at midnight on Fridays.<p>
+
+The facility is accessible from the Internet, but access to it is
+protected via a login name and password issued to each participating
+laboratory. You should keep this login name and password safe - don't
+email it around or display it on an Internet page. However, it is
+acceptable to allow your web browser to remember the password to make
+logging in each week easier and faster, and because the login name and
+password is shared by all staff within a participating lab, it is acceptable to
+write them down for reference. The passwords do not need to be changed
+periodically, but if you would like the password changed (for example,
+if you think it has been compromised or revealed to unauthorised parties),
+please contact CDB.<p>
+
+Please be aware that although each lab can only add or modify data
+their own data, each lab can download export files which
+contain all of the data submitted by all of the participating labs for that year.
+The facility was set up this way because: a) we assumed that each 
+participating lab would be interested in seeing respiratory virus activity 
+as reported by other labs; and b) it was quicker and cheaper to do it this
+way. Future versions of this facility may provide automated reports based
+on the data collected by it.<p>
+
+<h2>Submitting weekly data</h2>
+
+To use the facility, enter the URL provided by CDB 
+into your web browser (it may be convenient to bookmark the site to avoid having 
+to re-type the address each week. You may see a message about an invalid 
+certificate - ignore that message and proceed to access the web site regardless.<p>
+
+You will be prompted for a username and password, which will also have been supplied to you
+by CDB. Both the username and password are 
+case-sensitive. If your web browser offers to remember the password for you, by all means
+accept its offer.<p>
+
+After logging in, the first page is displayed. Your lab will have been automatically 
+selected, and you just need to select the date for the week for which you are about to enter or
+edit data. The date defaults to the previous Friday, and the last eight Fridays are
+given as options. When you have selected the appropriate date, click the <b>Next</b> button.<p>
+
+If you have already entered data for the selected week, those previously entered data will be 
+loaded and displayed - you can edit those data or complete the data entry process for that particular week. Otherwise blank
+data entry forms will be displayed. After you complete data entry on each page, click the 
+<b>Next</b> button to, perhaps unsurprisingly, proceed to the next page. Note that each time you click <b>Next</b> or 
+<b>Previous</b>, any new or changed data on the page you are leaving will be automatically saved to the
+database used by the facility. However, if you close your web browser or if your browser or computer crashes or there are
+network interruptions before you have navigated to the next or previous page, then it is possible that data you have 
+entered or edited on that page may not have been stored in the database. If such an event occurs, it is a good idea to 
+check that the data you have entered has been saved.<p>
+
+Each data entry page is fairly self-explanatory. Where counts are sought, only 
+integers (whole numbers) can be entered. It is best to use the <b>Tab</b> key to move 
+between fields, rather than the mouse. Pressing <b>Shift-Tab</b> will move backwards through the fields.<p>
+
+On the influenza cases page, the first letter of each type of test, type of influenza 
+and each sex can be typed in order to quickly select each one, rather than having to use the mouse.
+In the age field, age should be entered in whole years. However, for infants, ages in the form 
+4m or 4/12 (meaning four months), 3w or 3/52 (meaning 3 weeks) or 2d or 2/7 (meaning two days)
+are also accepted. In the suburb field, if a valid suburb name is typed, its postcode should 
+be automatically inserted. Spelling mistakes will prevent the postcode lookup from working
+correctly (this may be improved in a future version).<p>
+
+If you fill in all ten rows of cases, an additional page with an additional ten rows foor cases will automagically
+appear (and so on - there is no limit to how many cases can be entered for each week).
+
+On the fifth and final page, a summary of all the data entered for that week is shown. The <b>Print</b> 
+button will send a copy of this to your printer. If you are happy with the data, click the <b>Submit</b>
+button to mark it as complete and suitable for inclusion in reports. Note however that you can 
+still edit and revise data for your lab from up to eight weeks previously. Also, even if you do not click the 
+<b>Submit</b> button on teh final page, teh data you have entered will still have been stored in the database.
+The <b>Submit</b> button merely flags the data for a particular week as complete and suitable for inclusion in reports
+(although it can and should still be revised after submission, if necessary).<p>
+
+To log out, simple close the browser window or browser tab in which you have been accessing the facility.<p>
+
+<h2>Downloading data</h2>
+
+Each participating lab is able to download all of the data submitted by all participating labs for that year. This 
+facilitates analysis of the data by each lab, if they so choose. Future versions of the facility may offer 
+automated online analysis and reporting of the data. To download the data, simply click on one of the 
+<b>Download</b> buttons on the first page. Data will be downloaded as a CSV (comma-separated value) file, which can be 
+opened in Microsoft Excel or other reporting or analysis software packages.
+
+  </div>
+ </body>
+</html>
+
diff --git a/labsurv/app/helpers.js b/labsurv/app/helpers.js
new file mode 100644
index 0000000..a8729da
--- /dev/null
+++ b/labsurv/app/helpers.js
@@ -0,0 +1,138 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation and others. 
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+function addEvent (o, eventName, handler) {
+    if (o.addEventListener)
+        o.addEventListener(eventName, handler, false);
+    else
+        o.attachEvent("on" + eventName, handler);
+}
+
+/* If the current input is null, set it's value from the row before 
+ * if possible.
+ */
+function from_prev(input)
+{
+    if (input.value) return;
+    var td = input.parentNode;
+    if (td.cellIndex === undefined) return;
+    var tr = td.parentNode;
+    if (tr.sectionRowIndex === undefined || tr.sectionRowIndex < 1) return;
+    var section = tr.parentNode;
+    var prev_tr = section.rows[tr.sectionRowIndex-1];
+    if (prev_tr === undefined) return;
+    var prev_td = prev_tr.cells[td.cellIndex];
+    var prev_input = prev_td.firstChild;
+    if (!prev_input.value) return;
+    input.value = prev_input.value;
+}
+
+function evalJSONRequest(req) {
+    return eval("(" + req.responseText + ")");
+}
+    
+function gotPostcode(input, req)
+{
+    if (input.value) return;
+    input.value = evalJSONRequest(req);
+}
+
+
+function asyncReadyClosure (req, ctx, successCallback) 
+{
+    var nullFn = function () {};
+
+    return function () {
+        if (req.readyState == 4) {
+            var status = req.status;
+            req.onreadystatechange = nullFn;
+            if (status == 200 || status == 304)
+                successCallback(ctx, req);
+        }
+    }
+}
+
+function getXMLHttpRequest()
+{
+    return window.XMLHttpRequest
+        ? new XMLHttpRequest()
+        : new ActiveXObject("MSXML2.XMLHTTP.3.0");
+}
+
+function lookup_postcode(input)
+{
+    if (input.value) return;
+    var td = input.parentNode;
+    if (td.cellIndex === undefined || td.cellIndex < 1) return;
+    var tr = td.parentNode;
+    var prev_td = tr.cells[td.cellIndex - 1];
+    if (prev_td === undefined) return;
+    var prev_input = prev_td.firstChild;
+    if (!prev_input.value) return;
+    var url = input.form.action;
+    var req = getXMLHttpRequest();
+    req.open('POST', url, true);
+    req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+    req.onreadystatechange = asyncReadyClosure(req, input, gotPostcode);
+    req.send('lookup_suburb=' + prev_input.value);
+}
+
+/* Modern browsers typically synthesize a click on the next "submit" input when
+ * the user presses <enter> in a field. This is often undesireable in complex
+ * applications. This function attempts to intercept the <enter> key event and
+ * turn it into a click on a submit button of our choosing */
+function enterSubmit(form_name, input_name) {
+    function entersub(event) {
+        if (!event) event = window.event;
+        var inp = document.forms[form_name][input_name];
+        if (inp.length) inp = inp[0];
+        if (event && inp) {
+            var target = event.target ? event.target : event.srcElement;
+            var code = event.keyCode ? event.keyCode : event.which;
+//            if (code == 13) 
+//                alertargs(target.nodeName,target.type,inp);
+            if (code == 13 && !event.shiftKey 
+                    && target.type != 'submit' 
+                    && target.type != 'textarea' 
+                    && target.type != 'select-one') {
+                inp.focus();
+//                inp.click();
+                if (event.preventDefault)
+                    event.preventDefault();
+                else
+                    event.returnValue = false;
+                return false;
+            }
+        }
+        return true;
+    }
+    addEvent(document.body, 'keypress', entersub);
+}
+
+/* JS target to pop help in a new window
+ */
+function pophelp (path, target) {
+    var helpurl = path + '#' + target;
+    var height = screen.height / 2;
+    var width = screen.width / 2;
+    if (height < 400) height = 400;
+    if (width < 600) width = 600;
+    var features = "height=" + height + ",width=" + width +
+                ",resizeable=yes,scrollbars=yes,dependent=yes";
+    var w = window.open(helpurl, "Help", features);
+    w.focus();
+}
diff --git a/labsurv/app/images/banner.jpg b/labsurv/app/images/banner.jpg
new file mode 100644
index 0000000..6898fb1
Binary files /dev/null and b/labsurv/app/images/banner.jpg differ
diff --git a/labsurv/app/images/favicon.ico b/labsurv/app/images/favicon.ico
new file mode 100644
index 0000000..bae1c74
Binary files /dev/null and b/labsurv/app/images/favicon.ico differ
diff --git a/labsurv/app/images/help-g.png b/labsurv/app/images/help-g.png
new file mode 100644
index 0000000..f46bf4b
Binary files /dev/null and b/labsurv/app/images/help-g.png differ
diff --git a/labsurv/app/style.css b/labsurv/app/style.css
new file mode 100644
index 0000000..e6d671a
--- /dev/null
+++ b/labsurv/app/style.css
@@ -0,0 +1,185 @@
+/*
+ *  The contents of this file are subject to the HACOS License Version 1.2
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  Software distributed under the License is distributed
+ *  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ *  implied. See the LICENSE file for the specific language governing
+ *  rights and limitations under the License.  The Original Software
+ *  is "NetEpi Collection". The Initial Developer of the Original
+ *  Software is the Health Administration Corporation, incorporated in
+ *  the State of New South Wales, Australia.
+ *  
+ *  Copyright (C) 2004-2011 Health Administration Corporation and others. 
+ *  All Rights Reserved.
+ *
+ *  Contributors: See the CONTRIBUTORS file for details of contributions.
+ */
+
+body {
+    font-size: 10pt;
+    padding: 0;
+    margin: 0;
+}
+
+.banner {
+    background: #eee;
+    padding: 1em;
+    border-bottom: 2px solid #ccc;
+}
+
+.banner img {
+    float: left;
+    border: 2px solid #aaa;
+    margin-top: -0.5em;
+    margin-right: 1em;
+    margin-bottom: -0.5em;
+}
+
+.banner h1 {
+    margin: 0;
+    font-size: 130%;
+    font-weight: bold;
+}
+
+.banner h2 {
+    border-top: 2px solid #ccc;
+    margin: 0;
+    margin-top: 2px;
+}
+
+.banner .clear {
+    line-height: 1px;
+    display: block;
+    clear: both;
+}
+
+h2 {
+    font-size: 100%;
+    font-weight: bold;
+}
+
+.content {
+    padding: 1em;
+    clear: both;
+}
+
+.err-msg {
+    color: #c00;
+    background-color: #fee;
+    border: 1px solid #c00;
+    border-left: 0.5em solid #c00;
+    margin-top: 0.5ex;
+    font-weight: bold;
+    padding-left: 1ex;
+}
+.info-msg {
+    color: #080;
+    background-color: #efe;
+    border: 1px solid #080;
+    border-left: 0.5em solid #080;
+    margin-top: 0.5ex;
+    font-weight: bold;
+    padding-left: 1ex;
+}
+
+input[type="submit"], input[type="button"] {
+    width: 8em;
+    font-size: 83%;
+}
+input:focus {
+    background-color: #ffe;
+}
+ at media print {
+    input[type="submit"], input[type="button"], button#help {
+        display: none;
+    }
+}
+
+
+button#help {
+    float: right;
+    padding: 0;
+    background-color: transparent;
+    border: none;
+}
+button#help img {
+    padding: 0;
+    border: 0;
+    margin: 0;
+}
+
+.floatright {
+    display: block;
+    float: right;
+}
+
+.detail {
+    border-collapse: collapse;
+    width: 100%;
+}
+.detail th, .detail td {
+    /*border: 1px solid black;*/
+}
+.detail .rowlabel {
+    font-size: 83%;
+    font-weight: bold;
+    text-align: right;
+}
+.detail td {
+    text-align: center;
+}
+.detail input {
+    width: 90%;
+}
+
+.grid {
+    border-collapse: collapse;
+}
+.grid td {
+    border: 1px solid black;
+    padding: 2px;
+}
+.grid thead th {
+    border-left: 1px solid black;
+    border-right: 1px solid black;
+    border-bottom: 3px double black;
+    font-size: 83%;
+    background-color: #eee;
+    padding: 2px;
+}
+.grid thead td {
+    border: none;
+}
+.grid tbody th {
+    border-top: 1px solid black;
+    border-bottom: 1px solid black;
+    border-right: 3px double black;
+    text-align: right;
+    padding: 2px;
+    padding-right: 1ex;
+    font-size: 83%;
+    background-color: #eee;
+}
+.rtable td {
+    text-align: right;
+}
+
+.buttons {
+    clear: both;
+    width: 100%;
+    background: #eee;
+    padding: 0.5em;
+    border-top: 2px solid #ccc;
+}
+
+.export {
+    float: right;
+    background: #eee;
+    padding: 0.5em;
+    border: 2px solid #ccc;
+    margin: 0.5em;
+}
+.export h2 {
+    text-align: center;
+    margin-top: 0;
+}
diff --git a/labsurv/install.py b/labsurv/install.py
new file mode 100644
index 0000000..607c7c1
--- /dev/null
+++ b/labsurv/install.py
@@ -0,0 +1,96 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+topdir = os.path.normpath(os.path.dirname(__file__))
+sys.path.insert(0, topdir)
+from simpleinst import *
+
+# The simpleinst "config" object maintains a layered view of configuration
+# sources. On attribute access, the layers are searched for the named
+# attribute, and the topmost match returned.
+#
+# The layers are:
+#
+#    10. command line
+#    20. config.py (distibution defaults, local customisation)
+#    30. install.py (includes any explicitly set config attributes)
+#    40. platform defaults (from simpleinst.platform)
+#    50. defaults (from simpleinst.defaults)
+#
+# We add an import of the cgi_target config.py (run-time config) at priority 15
+# so that previous configuration directives are honoured.
+
+# Installer derived configuration (essentially defaults):
+config.appname = 'labsurv'
+assert config.appname == os.path.basename(config.appname),\
+    "appname must not contain pathname components"
+config.apptitle = 'NSW Health weekly respiratory virus laboratory surveillance'
+config.cgi_target = joinpath(config.cgi_dir, config.appname)
+config.html_target = joinpath(config.html_dir, config.appname)
+config.tracedb = False
+
+config.install_owner = 'root'
+config.session_secret = secret()
+config.dsn = '::%s:' % config.appname
+config.compile_py = True
+
+# Load any existing run-time config file
+config.source_file(15, 'config', config.cgi_target,
+                   exclude=['cgi_target', 'html_target'])
+
+config.write_file(joinpath(config.cgi_target, 'config.py'),
+                  exclude=[], 
+                  owner=config.web_user, 
+                  mode=0640)
+
+# Configure install hooks - delete (stale?) pyc files, optionally compile
+on_install('*.py', rm_pyc)
+if config.compile_py:
+    on_install('*.py', py_compile)
+
+appname_filter = Filter(config, pattern=r'{{APPNAME}}',
+                            subst=config.appname)
+
+# Sundry static content
+install(target = config.html_target, 
+        filter = appname_filter,
+        base = 'app', files = ['*.css', '*.js', '*.html'])
+install(target = joinpath(config.html_target, 'images'), 
+        base = 'app/images', files = ['*.jpg', '*.png', '*.ico'])
+
+
+# Applications
+install(target = config.cgi_target, 
+        filter = python_bang_path_filter,
+        base = 'app', files = ['app.py'], mode = 0755)
+
+# App modules and pages
+install(target = config.cgi_target, 
+        base = '.', files = ['pages'], 
+        include = ['*.py', '*.html'])
+
+# libraries
+install(target = config.cgi_target, 
+        base = '.', files = ['labsurv'], include='*.py')
+
+print """\
+*******************************************************************************
+Reminder - if you are using a deployment scheme that utilises a persistent
+application, such as mod_fastcgi or mod_python, you will now need to
+restart the application (how this is performed depends on the scheme -
+if running under apache, reloading it may suffice).
+"""
diff --git a/labsurv/labsurv/__init__.py b/labsurv/labsurv/__init__.py
new file mode 100644
index 0000000..1726c5a
--- /dev/null
+++ b/labsurv/labsurv/__init__.py
@@ -0,0 +1,16 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
diff --git a/labsurv/labsurv/dbapi.py b/labsurv/labsurv/dbapi.py
new file mode 100644
index 0000000..9355c97
--- /dev/null
+++ b/labsurv/labsurv/dbapi.py
@@ -0,0 +1,159 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Paper over the differences between DB-API2 implementations
+"""
+
+from time import time
+
+import config
+
+# What database prefers for boolean columns
+TRUE = True
+FALSE = False
+
+connect_extra = {}
+try:
+    from ocpgdb import *
+    connect_extra = dict(use_mx_datetime=True)
+except ImportError:
+    from pyPgSQL import PgSQL
+    PgSQL.useUTCtimeValue = True       # Works around brokeness in some vers
+    PgSQL.fetchReturnsList = True      # faster, and duplicates dbobj work
+    from pyPgSQL.PgSQL import *
+    Binary = PgBytea
+    # pyPgSQL predates python True and False
+    TRUE = PG_True
+    FALSE = PG_False
+
+# Some fine-grained exceptions. Not part of the API, but this is a convenient
+# place to define them.
+class IdentifierError(DatabaseError): pass
+class ValidationError(DatabaseError): pass
+class DuplicateKeyError(OperationalError): pass
+class ConstraintError(OperationalError): pass
+class TooManyRecords(OperationalError): pass
+class RecordDeleted(OperationalError): pass
+
+debug = False
+
+def execute_debug(value):
+    global debug
+    debug = value
+
+debug_prefix = '+'
+
+def execute(curs, cmd, *args):
+    def pretty_cmd(cmd, args):
+        if len(args) == 1 and type(args[0]) in (tuple, list):
+            args = args[0]
+        cleanargs = []
+        for arg in args:
+            arg = str(arg)
+            if len(arg) > 30:
+                arg = arg[:15] + '...' + arg[-10:]
+            cleanargs.append(arg)
+        cmdargs = cmd % tuple(cleanargs)
+        return debug_prefix + cmdargs.replace('\n', '\n' + debug_prefix)
+
+    if ';' in cmd:      # Crude security hack
+        raise ProgrammingError('Only one command per execute() allowed')
+    if debug:
+        st = time()
+    try:
+        res = curs.execute(cmd, *args)
+    except Error, e:
+        exc_type, exc_value, exc_tb = sys.exc_info()
+        if 'duplicate key' in str(exc_value):
+            exc_type = DuplicateKeyError
+        elif 'violates foreign key constraint' in str(exc_value):
+            exc_type = ConstraintError
+#        sys.stderr.write('dbapi.error: %s: %s\n' % (e, pretty_cmd(cmd, args)))
+        try:
+            raise exc_type, '%s%s' % (exc_value, pretty_cmd(cmd, args)), exc_tb
+        finally:
+            del exc_type, exc_value, exc_tb
+    except Exception:
+        sys.stderr.write('Exception SQL: %s\n' % pretty_cmd(cmd, args))
+        raise
+    else:
+        if debug: 
+            sys.stderr.write(pretty_cmd(cmd, args) + 
+                                ' (%.3f secs)\n' % (time() - st))
+        return res
+
+class O(object): pass
+
+
+class Cursor:
+    def __init__(self, cursor):
+        self.cursor = cursor
+
+    def execute(self, cmd, *args):
+        execute(self.cursor, cmd, *args)
+        self.cols = None
+        if self.cursor.description:
+            self.cols = [d[0] for d in self.cursor.description]
+
+    def row_obj(self, row):
+        o = O()
+        for col, value in zip(self.cols, row):
+            setattr(o, col, value)
+        return o
+
+    def yield_obj(self):
+        for row in self.cursor.fetchmany(200):
+            yield self.row_obj(row)
+
+    def one_obj(self):
+        row = self.cursor.fetchone()
+        if row is not None:
+            return self.row_obj(row)
+
+    def close(self):
+        self.cursor.close()
+
+
+class DB:
+    def __init__(self):
+        dsn = 'host:port:database:user'
+        args = dict(connect_extra)
+        for name, value in zip(dsn.split(':'), config.dsn.split(':')):
+            if value:
+                args[name] = value
+        self.db = connect(**args)
+
+    def commit(self):
+        if debug:
+            sys.stderr.write(debug_prefix + 'COMMIT\n')
+        self.db.commit()
+
+    def rollback(self):
+        if debug:
+            sys.stderr.write(debug_prefix + 'ROLLBACK\n')
+        self.db.rollback()
+
+    def close(self):
+        if debug:
+            sys.stderr.write(debug_prefix + 'CLOSE\n')
+        self.db.close()
+
+    def cursor(self):
+        return self.db.cursor()
+
+db = DB()
diff --git a/labsurv/labsurv/labsurv.py b/labsurv/labsurv/labsurv.py
new file mode 100644
index 0000000..470eac0
--- /dev/null
+++ b/labsurv/labsurv/labsurv.py
@@ -0,0 +1,686 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Standard library
+import csv
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+# 3rd Party Libs
+from mx import DateTime
+
+# Application libs
+from dbapi import db, execute
+
+class ReportError(Exception): pass
+
+labs = [
+    ('CHW',             'Children\'s Hospital at Westmead'),
+    ('ICPMR',           'ICPMR'),
+    ('SWAPS',           'SWAPS'),
+    ('SEALS',           'SEALS'),
+    ('PaLMS',           'PaLMS'),
+    ('HAPS',            'HAPS'),
+    ('RPAH',            'RPAH'),
+    ('DHM',             'DHM'),
+    ('Symbion',         'Symbion'),
+    ('StVincents',      'St Vincents'),
+]
+
+tests = [
+    ('DIF', 'DIF'),
+    ('Culture', 'Culture'),
+    ('PCR', 'PCR'),
+    ('SerologyFFR', 'Serology - four fold rise'),
+    ('SerologySHT', 'Serology - single high titre'),
+    ('POC', 'Point of Care'),
+]
+
+choose = ('', 'Choose...')
+
+
+def iso_to_mx_date(date):
+    if date:
+        return DateTime.strptime(date, '%Y-%m-%d')
+
+
+def mx_to_iso_date(date):
+    if date:
+        return date.strftime('%Y-%m-%d')
+
+def mx_to_iso_datetime(date):
+    if date:
+        return date.strftime('%Y-%m-%d %H:%M')
+
+
+# Tim: Medical shorthand often uses "3/52" to mean 3 weeks, "4/12" to mean 4
+# months, "5/7" to mean 5 days, "13/24" to mean 13 hours, but not "43/60"
+# because that could be minutes or seconds. But 3wks or 3w, 4mths or 4m, 5d,
+# 13hrs etc are also used. You could support some or all of these conventions,
+# plus interpret integers or floats as years. Anything other than years should
+# be converted to a float (in years). Sometimes things like "3y 3m" is used to
+# mean 3.25 yrs.
+
+age_formats = [
+    (12, ('m', 'mth', 'mths', 'month', 'months')),
+    (52, ('w', 'wk', 'wks', 'week', 'weeks')),
+    (1, ('y', 'yr', 'yrs', 'year', 'years')),
+    (365.25, ('d', 'day', 'days')),
+]
+
+def age_to_float(age):
+    if not age:
+        return None
+    age = age.strip()
+    if not age:
+        return None
+    try:
+        if '/' in age:
+            a, b = age.split('/', 1)
+            a = float(a.rstrip())
+            b = int(b.lstrip())
+            if b in (12, 52):
+                return a / b
+            elif b == 7:
+                return a / 365.25
+            elif b == 24:
+                return a / (365.25 * 24)
+            raise ValueError
+        for divisor, suffixes in age_formats:
+            for suffix in suffixes:
+                if age.lower().endswith(suffix):
+                    return float(age[:-len(suffix)].rstrip()) / divisor
+        return float(age)
+    except ValueError:
+        raise ReportError('Unknown age format: %r' % age)
+
+
+def float_to_age(age):
+    if not age or age < 0:
+        return ''
+    if age >= 1:
+        return '%.0f' % age
+    months = age * 12
+    if months >= 2:
+        return '%.0fm' % months
+    weeks = age * 52
+    if weeks >= 1:
+        return '%.0fw' % (age * 52)
+    return '0'
+
+
+def chunk(iter, chunksize=100):
+    chunk = []
+    for row in iter:
+        if len(chunk) == chunksize:
+            yield chunk
+            chunk = []
+        chunk.append(row)
+    if chunk:
+        yield chunk
+
+
+class Monitor(object):
+    """
+    Monitor a set of instance attribues for change
+    """
+    __slots__ = 'inst', 'attrs', 'state'
+    def __init__(self, inst, *attrs):
+        self.inst = inst
+        self.attrs = attrs
+        self.clear()
+
+    def clear(self):
+        state = {}
+        for attr in self.attrs:
+            state[attr] = getattr(self.inst, attr)
+        self.state = state
+
+    def check(self):
+        for attr in self.attrs:
+            then = self.state[attr]
+            now = getattr(self.inst, attr)
+            if (then or now) and then != now:
+                return True
+        return False
+
+
+class ReportMixin:
+    report_cols = 'report_id', 'lab', 'week', 'notes', 'completed'
+
+    def init_report(self, lab=None):
+        self.report_id = None
+        self.notes = ''
+        self.completed = None
+
+        self.lab_options = list(labs)
+        self.lab_options.insert(0, choose)
+        if lab:
+            for name, label in labs:
+                if name.lower() == lab.lower():
+                    lab = name
+                    break
+            else:
+                lab = None
+        if lab:
+            self.lab = lab
+            self.lab_readonly = True
+        else:
+            self.lab = self.lab_options[0][0]
+            self.lab_readonly = False
+
+        prev_week = DateTime.RelativeDateTime(weeks=-1) 
+        self.week_options = []
+        d = DateTime.now() + \
+            DateTime.RelativeDateTime(days=-4, weekday=(DateTime.Friday, 0), 
+                                      hour=0, minute=0, second=0)
+        for n in range(8):
+            self.week_options.append(d.strftime('%Y-%m-%d'))
+            d += prev_week
+        self.week_options.reverse()
+        self.week = self.week_options[-1]
+        self.__monitor = Monitor(self, *self.report_cols)
+
+    def load_report(self):
+        if not self.lab:
+            raise ReportError('Please select a lab')
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT report_id, completed, notes'
+                          ' FROM lab_reports WHERE lab=%s AND week=%s',
+                    self.lab, iso_to_mx_date(self.week))
+            row = curs.fetchone()
+            if not row:
+                return False
+            self.report_id, self.completed, self.notes = row
+            self.__monitor.clear()
+            return True
+        finally:
+            curs.close()
+
+    def new_report(self):
+        assert self.lab
+        assert self.week
+        curs = db.cursor()
+        try:
+            execute(curs, 'INSERT INTO lab_reports (lab, week) VALUES (%s, %s)',
+                    self.lab, iso_to_mx_date(self.week))
+        finally:
+            curs.close()
+
+    def update_report(self):
+        assert self.report_id
+        if self.__monitor.check():
+            curs = db.cursor()
+            try:
+                execute(curs, 'UPDATE lab_reports SET completed=%s, notes=%s'
+                            ' WHERE report_id=%s',
+                        self.completed, self.notes, self.report_id)
+            finally:
+                curs.close()
+            db.commit()
+            self.__monitor.clear()
+
+    def reports(self):
+        cols = 'report_id', 'lab', 'week', 'completed'
+        class Report(object): __slots__ = cols
+        reports = []
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT %s FROM lab_reports ORDER BY week, lab' % 
+                            ','.join(cols))
+            for row in curs.fetchall():
+                report = Report()
+                for a, v in zip(cols, row):
+                    setattr(report, a, v)
+                reports.append(report)
+        finally:
+            curs.close()
+        return reports
+
+    def export_notes(self):
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT week, lab, completed, notes FROM lab_reports'
+                          ' ORDER BY week, lab')
+            rows = curs.fetchall()
+        finally:
+            curs.close()
+        yield ['Week Ending', 'Lab', 'Completed', 'Notes']
+        for week, lab, completed, notes in rows:
+            yield (mx_to_iso_date(week), lab, 
+                   mx_to_iso_datetime(completed), notes)
+        
+
+class TestTotals:
+    tests = [
+        ('DIF', 'By DIF'),
+        ('Culture', 'By culture'),
+        ('Serology', 'By serology'),
+        ('PCR', 'By PCR'),
+        ('POC', 'By Point-of-Care tests'),
+        ('Who', 'Sent to WHO'),
+    ]
+
+    def __init__(self, name, label):
+        self.name = name
+        self.label = label
+        self.count = self.load_count = None
+
+
+class TotalsMixin:
+
+    def totals_set_initial(self):
+        for tt in self.test_totals:
+            tt.load_count = tt.count
+
+    def init_test_totals(self):
+        self.test_totals = [TestTotals(*t) for t in TestTotals.tests]
+        self.totals_set_initial()
+
+    def load_totals(self):
+        assert self.report_id
+        tt_by_name = {}
+        for tt in self.test_totals:
+            tt_by_name[tt.name] = tt
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT test, count FROM lab_totals'
+                          ' WHERE report_id=%s', self.report_id)
+            for test, count in curs.fetchall():
+                try:
+                    tt = tt_by_name[test]
+                except KeyError:
+                    continue
+                tt.count = count
+        finally:
+            curs.close()
+        self.totals_set_initial()
+
+    def update_totals(self):
+        assert self.report_id
+        updated = False
+        curs = db.cursor()
+        try:
+            for tt in self.test_totals:
+                if tt.count:
+                    try:
+                        tt.count = int(tt.count)
+                    except ValueError:
+                        raise ReportError('%s count must be an integer' %
+                                          tt.label)
+                else:
+                    tt.count = None
+                if tt.count == tt.load_count:
+                    continue
+                execute(curs, 'UPDATE lab_totals SET count=%s'
+                              ' WHERE report_id=%s AND test=%s',
+                              (tt.count, self.report_id, tt.name))
+                if not curs.rowcount:
+                    execute(curs, 'INSERT INTO lab_totals VALUES (%s,%s,%s)',
+                            (self.report_id, tt.name, tt.count))
+                updated = True
+        finally:
+            curs.close()
+        if updated:
+            db.commit()
+            self.totals_set_initial()
+
+    def export_totals(self):
+        reports = self.reports()
+        totals_by_report = {}
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT report_id, test, count FROM lab_totals')
+            for report_id, test, count in curs.fetchall():
+                totals_by_report.setdefault(report_id, {})[test] = count
+        finally:
+            curs.close()
+        heading = ['Week Ending', 'Lab', 'Completed']
+        for n, l in TestTotals.tests:
+            heading.append(n)
+        yield heading
+        for report in reports:
+            row = [mx_to_iso_date(report.week), report.lab,
+                   mx_to_iso_datetime(report.completed)]
+            totals = totals_by_report.get(report.report_id, {})
+            for n, l in TestTotals.tests:
+                row.append(totals.get(n, ''))
+            yield row
+
+
+class TestDiags:
+    tests = tests
+
+    diagnoses = [
+        ('FluA H1', 'Flu A (H1)'),
+        ('FluA H3', 'Flu A (H3)'),
+        ('FluA H1N1/09', 'Flu H1N1 (Swine)'),
+        ('FluA', 'Flu A not subtyped'),
+        ('FluB', 'Flu B'),
+        ('Adeno', 'Adeno'),
+        ('Paraflu', 'Paraflu 1, 2 or 3'),
+        ('RSV', 'RSV'),
+        ('Rhino', 'Rhino'),
+    ]
+    diagnosis_map = {}
+    for i, (name, label) in enumerate(diagnoses):
+        diagnosis_map[name] = i
+
+    def __init__(self, name, label):
+        self.name = name
+        self.label = label
+        self.counts = [None] * len(self.diagnoses)
+
+
+class DiagsMixin:
+
+    def diag_set_initial(self):
+        for td in self.test_diags:
+            td.load_counts = list(td.counts)
+        
+    def init_test_diags(self):
+        self.test_diags = [TestDiags(*t) for t in TestDiags.tests]
+        self.diag_set_initial()
+
+    def load_diags(self):
+        assert self.report_id
+        td_by_name = {}
+        for td in self.test_diags:
+            td_by_name[td.name] = td
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT test, diagnosis, count FROM lab_diags'
+                          ' WHERE report_id=%s', self.report_id)
+            for test, diagnosis, count in curs.fetchall():
+                try:
+                    td = td_by_name[test]
+                    d_index = TestDiags.diagnosis_map[diagnosis]
+                except KeyError:
+                    continue
+                td.counts[d_index] = count
+        finally:
+            curs.close()
+        self.diag_set_initial()
+
+    def update_diags(self):
+        assert self.report_id
+        updated = False
+        curs = db.cursor()
+        try:
+            for td in self.test_diags:
+                for i, c in enumerate(td.counts):
+                    diagnosis = TestDiags.diagnoses[i][0]
+                    if c:
+                        try:
+                            td.counts[i] = c = int(c)
+                        except ValueError:
+                            raise ReportError('%s, %s count must be an integer'
+                                                % (td.name, diagnosis))
+                    else:
+                        td.counts[i] = c = None
+                    if c == td.load_counts[i]:
+                        continue
+                    execute(curs, 'UPDATE lab_diags SET count=%s WHERE'
+                                  ' report_id=%s AND test=%s AND diagnosis=%s',
+                                (c, self.report_id, td.name, diagnosis))
+                    if not curs.rowcount:
+                        execute(curs, 'INSERT INTO lab_diags'
+                                      ' VALUES (%s,%s,%s,%s)',
+                                (self.report_id, td.name, diagnosis, c))
+                    updated = True
+        finally:
+            curs.close()
+        if updated:
+            db.commit()
+            self.diag_set_initial()
+
+    def export_diags(self):
+        reports = self.reports()
+        diags_by_report = {}
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT report_id, test, diagnosis, count'
+                          ' FROM lab_diags')
+            for report_id, test, diagnosis, count in curs.fetchall():
+                diags = diags_by_report.setdefault(report_id, {})
+                diags[(test, diagnosis)] = count
+        finally:
+            curs.close()
+        heading = ['Week Ending', 'Lab', 'Completed']
+        for test_name, test_label in TestDiags.tests:
+            for diag_name, diag_label in TestDiags.diagnoses:
+                heading.append('%s_%s' % (test_name, diag_name))
+        yield heading
+        for report in reports:
+            row = [mx_to_iso_date(report.week), report.lab,
+                   mx_to_iso_datetime(report.completed)]
+            totals = diags_by_report.get(report.report_id, {})
+            for test_name, test_label in TestDiags.tests:
+                for diag_name, diag_label in TestDiags.diagnoses:
+                    row.append(totals.get((test_name, diag_name), ''))
+            yield row
+
+
+class PositiveCase:
+    tests = [choose] + tests
+    sexes = ['', 'M', 'F']
+    attrs = 'test', 'diagnosis', 'age', 'sex', 'suburb', 'postcode'
+    diagnoses = ['', 'A', 'B', 'H1N1 Swine']
+
+    def __init__(self, idx):
+        self.idx = idx
+        self.test = ''
+        self.diagnosis = ''
+        self.age = ''
+        self.sex = ''
+        self.suburb = ''
+        self.postcode = ''
+        self.monitor = Monitor(self, *self.attrs)
+
+    def from_db(self, row):
+        for a, v in zip(self.attrs, row):
+            if a == 'age':
+                v = float_to_age(v)
+            setattr(self, a, v)
+        self.monitor.clear()
+
+    def update(self, curs, report_id):
+        set_sql = []
+        args = []
+        for a in self.attrs:
+            v = getattr(self, a)
+            if a == 'age':
+                v = age_to_float(v)
+            set_sql.append('%s=%%s' % a)
+            args.append(v)
+        args.append(report_id)
+        args.append(self.idx)
+        execute(curs, 'UPDATE lab_cases SET %s WHERE'
+                        ' report_id=%%s AND idx=%%s'
+                    % (', '.join(set_sql)), args)
+        if not curs.rowcount:
+            cols = list(self.attrs) + ['report_id', 'idx']
+            fmt = ['%s'] * len(cols)
+            execute(curs, 'INSERT INTO lab_cases (%s) VALUES (%s)'
+                    % (','.join(cols), ','.join(fmt)), args)
+
+    def check(self):
+        if self:
+            age = age_to_float(self.age)
+            if age and not 0 <= age < 120:
+                raise ReportError('Invalid age: %.0f years' % age)
+
+    def __nonzero__(self):
+        return bool(self.age or self.suburb or self.postcode)
+
+
+class CasesMixin:
+    def init_positive_cases(self):
+        self.case_page_size = 10
+        self.case_page = 0
+        self.positive_cases = []
+        self.add_case_page()
+
+    def load_positive_cases(self):
+        assert self.report_id
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT idx, %s FROM lab_cases WHERE report_id=%%s' %
+                            ', '.join(PositiveCase.attrs), self.report_id)
+            for row in curs.fetchall():
+                idx = row[0]
+                while len(self.positive_cases) <= idx:
+                    self.add_case_page()
+                self.positive_cases[idx].from_db(row[1:])
+        finally:
+            curs.close()
+
+    def update_positive_cases(self):
+        assert self.report_id
+        curs = db.cursor()
+        try:
+            updated = False
+            for case in self.positive_cases:
+                if case.monitor.check():
+                    case.update(curs, self.report_id)
+                    updated = True
+        finally:
+            curs.close()
+        if updated:
+            db.commit()
+            for case in self.positive_cases:
+                case.monitor.clear()
+
+    def add_case_page(self):
+        for n in xrange(self.case_page_size):
+            i = len(self.positive_cases)
+            self.positive_cases.append(PositiveCase(i))
+
+    def positive_case_page(self):
+        s = self.case_page_size * self.case_page
+        e = s + self.case_page_size
+        return self.positive_cases[s:e]
+
+    def case_page_info(self):
+        return 'Page %d of %d' % (self.case_page + 1,
+                                len(self.positive_cases) / self.case_page_size)
+
+    def case_page_empty(self):
+        for case in self.positive_case_page():
+            if case:
+                return False
+        return True
+
+    def case_page_full(self):
+        for case in self.positive_case_page():
+            if not case:
+                return False
+        return True
+
+    def check_page(self):
+        for case in self.positive_case_page():
+            case.check()
+
+    def next_case_page(self):
+#        if not self.case_page_full():
+#            return False
+        # Last slot filled?
+        if not self.positive_case_page()[-1]:
+            return False
+        self.case_page += 1
+        if len(self.positive_cases) <= (self.case_page * self.case_page_size):
+            self.add_case_page()
+        return True
+
+    def prev_case_page(self):
+        if self.case_page:
+            if self.case_page_empty():
+                del self.positive_cases[-self.case_page_size:]
+            self.case_page -= 1
+            return True
+        else:
+            return False
+
+    def export_cases(self):
+        reports = self.reports()
+        cols = ['idx'] + list(PositiveCase.attrs)
+        cases_by_report = {}
+        curs = db.cursor()
+        try:
+            execute(curs, 'SELECT report_id, %s FROM lab_cases' %
+                            ', '.join(cols))
+            for row in curs.fetchall():
+                cases = cases_by_report.setdefault(row[0], [])
+                cases.append(row[1:])
+        finally:
+            curs.close()
+        heading = ['Week Ending', 'Lab', 'Completed']
+        for col in cols:
+            heading.append(col.title())
+        yield heading
+        for report in reports:
+            cases = cases_by_report.get(report.report_id)
+            if cases:
+                cases.sort()
+                report_row = [mx_to_iso_date(report.week), report.lab, 
+                              mx_to_iso_datetime(report.completed)]
+                for case in cases:
+                    row = list(report_row)
+                    for a, v in zip(col, case):
+                        if a == 'age':
+                            v = float_to_age(v)
+                        row.append(v)
+                    yield row
+
+
+class LabSurv(ReportMixin, TotalsMixin, DiagsMixin, CasesMixin):
+    """
+    Represents an individual lab surveillance report.
+    """
+
+    def __init__(self, lab=None):
+        self.init_report(lab)
+
+    def load(self):
+        # Operator has chosen lab & week, load any prior report
+        self.init_test_totals()
+        self.init_test_diags()
+        self.init_positive_cases()
+        loaded = self.load_report()
+        if not loaded:
+            self.new()
+            self.load_report()
+        # Now load the rest
+        self.load_totals()
+        self.load_diags()
+        self.load_positive_cases()
+        return loaded
+
+    def new(self):
+        self.new_report()
+        db.commit()
+
+    def submit(self):
+        self.completed = DateTime.now()
+        self.update_report()
+
+    def export(self, mode):
+        meth = getattr(self, 'export_' + mode)
+        return list(meth())
diff --git a/labsurv/labsurv/pcode.py b/labsurv/labsurv/pcode.py
new file mode 100644
index 0000000..c1972f9
--- /dev/null
+++ b/labsurv/labsurv/pcode.py
@@ -0,0 +1,14737 @@
+
+# Locality to postcode mapping
+# Derived from http://www1.auspost.com.au/postcodes/
+
+locality_to_postcode = {
+    'DARWIN': '0800',
+    'ALAWA': '0810',
+    'BRINKIN': '0810',
+    'CASUARINA': '0810',
+    'COCONUT GROVE': '0810',
+    'JINGILI': '0810',
+    'LEE POINT': '0810',
+    'LYONS': '0810',
+    'MILLNER': '0810',
+    'MOIL': '0810',
+    'MUIRHEAD': '0810',
+    'NAKARA': '0810',
+    'NIGHTCLIFF': '0810',
+    'RAPID CREEK': '0810',
+    'TIWI': '0810',
+    'WAGAMAN': '0810',
+    'WANGURI': '0810',
+    'ANULA': '0812',
+    'BUFFALO CREEK': '0812',
+    'HOLMES': '0812',
+    'KARAMA': '0812',
+    'LEANYER': '0812',
+    'MALAK': '0812',
+    'MARRARA': '0812',
+    'NORTHLAKES': '0812',
+    'SANDERSON': '0812',
+    'WOODLEIGH GARDENS': '0812',
+    'WULAGI': '0812',
+    'BAGOT': '0820',
+    'BAYVIEW': '0820',
+    'CHARLES DARWIN': '0820',
+    'COONAWARRA': '0820',
+    'CULLEN BAY': '0820',
+    'DARWIN DC': '0820',
+    'DARWIN INTERNATIONAL AIRPORT': '0820',
+    'DARWIN MC': '0820',
+    'EAST POINT': '0820',
+    'EATON': '0820',
+    'FANNIE BAY': '0820',
+    'LARRAKEYAH': '0820',
+    'LUDMILLA': '0820',
+    'PARAP': '0820',
+    'RAAF BASE DARWIN': '0820',
+    'STUART PARK': '0820',
+    'THE GARDENS': '0820',
+    'THE NARROWS': '0820',
+    'WINNELLIE': '0820',
+    'WOOLNER': '0820',
+    'BERRIMAH': '0828',
+    'KNUCKEY LAGOON': '0828',
+    'HOLTZE': '0829',
+    'PINELANDS': '0829',
+    'ARCHER': '0830',
+    'DRIVER': '0830',
+    'DURACK': '0830',
+    'FARRAR': '0830',
+    'GRAY': '0830',
+    'MARLOW LAGOON': '0830',
+    'MOULDEN': '0830',
+    'PALMERSTON': '0830',
+    'SHOAL BAY': '0830',
+    'WOODROFFE': '0830',
+    'YARRAWONGA': '0830',
+    'BAKEWELL': '0832',
+    'BELLAMACK': '0832',
+    'GUNN': '0832',
+    'JOHNSTON': '0832',
+    'MITCHELL': '0832',
+    'ROSEBERY': '0832',
+    'ROSEBERY HEIGHTS': '0832',
+    'ZUCCOLI': '0832',
+    'COOLALINGA': '0835',
+    'HOWARD SPRINGS': '0835',
+    'VIRGINIA': '0835',
+    'GIRRAWEEN': '0836',
+    'HERBERT': '0836',
+    'HUMPTY DOO': '0836',
+    'MANTON': '0837',
+    'NOONAMAH': '0837',
+    'BERRY SPRINGS': '0838',
+    'DUNDEE BEACH': '0840',
+    'DUNDEE DOWNS': '0840',
+    'DUNDEE FOREST': '0840',
+    'DARWIN RIVER': '0841',
+    'BATCHELOR': '0845',
+    'ADELAIDE RIVER': '0846',
+    'PINE CREEK': '0847',
+    'COSSACK': '0850',
+    'EMUNGALAN': '0850',
+    'KATHERINE': '0850',
+    'KATHERINE EAST': '0850',
+    'KATHERINE SOUTH': '0850',
+    'LANSDOWNE': '0850',
+    'ARNOLD': '0852',
+    'BAINES': '0852',
+    'BARUNGA': '0852',
+    'BESWICK': '0852',
+    'BESWICK CREEK': '0852',
+    'BINJARI': '0852',
+    'BIRDUM': '0852',
+    'BRADSHAW': '0852',
+    'BUCHANAN': '0852',
+    'BULMAN WEEMOL': '0852',
+    'CRESWELL': '0852',
+    'DAGURAGU': '0852',
+    'DALY WATERS': '0852',
+    'DELAMERE': '0852',
+    'DUNMARRA': '0852',
+    'EDITH': '0852',
+    'ELSEY': '0852',
+    'ELSEY STATION': '0852',
+    'FLORINA': '0852',
+    'FLYING FOX': '0852',
+    'GREGORY': '0852',
+    'GULUNG MARDRULK': '0852',
+    'GURINDJI': '0852',
+    'JILKMINGGAN': '0852',
+    'KALKARINDJI': '0852',
+    'LAJAMANU': '0852',
+    'LARRIMAH': '0852',
+    'LIMMEN': '0852',
+    'MANBULLOO': '0852',
+    'MARANBOY': '0852',
+    'MATARANKA': '0852',
+    'MCARTHUR': '0852',
+    'MINIYERI': '0852',
+    'NGUKURR': '0852',
+    'NITMILUK': '0852',
+    'NUMBULWAR': '0852',
+    'PELLEW ISLANDS': '0852',
+    'PIGEON HOLE': '0852',
+    'ROBINSON RIVER': '0852',
+    'STURT PLATEAU': '0852',
+    'TANAMI EAST': '0852',
+    'TIMBER CREEK': '0852',
+    'TOP SPRINGS': '0852',
+    'URALLA': '0852',
+    'VENN': '0852',
+    'VICTORIA RIVER': '0852',
+    'VICTORIA RIVER DOWNS': '0852',
+    'WARUMUNGU': '0852',
+    'WAVE HILL': '0852',
+    'WILTON': '0852',
+    'YARRALIN': '0852',
+    'TINDAL': '0853',
+    'BORROLOOLA': '0854',
+    'KING ASH BAY': '0854',
+    'TENNANT CREEK': '0860',
+    'AVON DOWNS': '0862',
+    'CALVERT': '0862',
+    'CRESSWELL DOWNS': '0862',
+    'ELLIOTT': '0862',
+    'HELEN SPRINGS': '0862',
+    'MUCKATY STATION': '0862',
+    'NEWCASTLE WATERS': '0862',
+    'NICHOLSON': '0862',
+    'PAMAYU': '0862',
+    'PHILLIP CREEK STATION': '0862',
+    'RENNER SPRINGS': '0862',
+    'TABLELANDS': '0862',
+    'THREE WAYS': '0862',
+    'WARREGO': '0862',
+    'WOLLOGORANG STATION': '0862',
+    'WYCLIFFE WELL': '0862',
+    'ALICE SPRINGS': '0870',
+    'ARALUEN': '0870',
+    'ARUMBERA': '0870',
+    'BRAITLING': '0870',
+    'CICCONE': '0870',
+    'CONNELLAN': '0870',
+    'DESERT SPRINGS': '0870',
+    'EAST SIDE': '0870',
+    'FLYNN': '0870',
+    'GILLEN': '0870',
+    'ILPARPA': '0870',
+    'IRLPME': '0870',
+    'LARAPINTA': '0870',
+    'MOUNT JOHNS': '0870',
+    'ROSS': '0870',
+    'SADADEEN': '0870',
+    'STUART': '0870',
+    'THE GAP': '0870',
+    'UNDOOLYA': '0870',
+    'WHITE GUMS': '0870',
+    'AHERRENGE': '0872',
+    'ALI CURUNG': '0872',
+    'AMATA': '0872',
+    'AMOONGUNA': '0872',
+    'AMPILATWATJA': '0872',
+    'ANATYE': '0872',
+    'ANMATJERE': '0872',
+    'ANTEWENEGERRDE': '0872',
+    'AREYONGA': '0872',
+    'ATITJERE': '0872',
+    'AYERS ROCK': '0872',
+    'BARROW CREEK': '0872',
+    'BURT PLAIN': '0872',
+    'CANTEEN CREEK': '0872',
+    'CHILLA WELL': '0872',
+    'COSTELLO': '0872',
+    'DAVENPORT': '0872',
+    'DOCKER RIVER': '0872',
+    'ENGAWALA': '0872',
+    'ERLDUNDA': '0872',
+    'ERNABELLA': '0872',
+    'FINKE': '0872',
+    'FREGON': '0872',
+    'GHAN': '0872',
+    'GIBSON DESERT NORTH': '0872',
+    'GIBSON DESERT SOUTH': '0872',
+    'HAASTS BLUFF': '0872',
+    'HALE': '0872',
+    'HART': '0872',
+    'HART RANGE': '0872',
+    'HERMANNSBURG': '0872',
+    'HUGH': '0872',
+    'IMANPA': '0872',
+    'INDULKANA': '0872',
+    'JAY CREEK': '0872',
+    'KALTUKATJARA': '0872',
+    'KINTORE': '0872',
+    'KIWIRRKURRA': '0872',
+    'KULGERA': '0872',
+    'KUNPARRKA': '0872',
+    'LAKE MACKAY': '0872',
+    'LARAMBA': '0872',
+    'MEREENIE': '0872',
+    'MIMILI': '0872',
+    'MOUNT LIEBIG': '0872',
+    'MOUNT ZEIL': '0872',
+    'MULGA BORE': '0872',
+    'MURPUTJA HOMELANDS': '0872',
+    'MUTITJULU': '0872',
+    'NAMATJIRA': '0872',
+    'NGAANYATJARRAGILES': '0872',
+    'NYAPARI': '0872',
+    'NYIRRIPI': '0872',
+    'PAPUNYA': '0872',
+    'PATJARR': '0872',
+    'PETERMANN': '0872',
+    'PITJANTJATJARA HOMELANDS': '0872',
+    'SANDOVER': '0872',
+    'SANTA TERESA': '0872',
+    'SIMPSON': '0872',
+    'TANAMI': '0872',
+    'TARA': '0872',
+    'THANGKENHARENGE': '0872',
+    'TI TREE': '0872',
+    'TITJIKALA': '0872',
+    'TJIRRKARLI': '0872',
+    'TJUKURLA': '0872',
+    'ULURU': '0872',
+    'UMPANGARA': '0872',
+    'URAPUNTJA': '0872',
+    'WALLACE ROCKHOLE': '0872',
+    'WANARN': '0872',
+    'WILLOWRA': '0872',
+    'WILORA': '0872',
+    'WINGELLINA': '0872',
+    'WUTUNUGURRA': '0872',
+    'YUELAMU': '0872',
+    'YUENDUMU': '0872',
+    'YULARA': '0872',
+    'GAPUWIYAK': '0880',
+    'GOVE': '0880',
+    'GUNYANGARA': '0880',
+    'NHULUNBUY': '0880',
+    'YIRRKALA': '0880',
+    'ALYANGULA': '0885',
+    'JABIRU': '0886',
+    'BARANGAROO': '2000',
+    'DAWES POINT': '2000',
+    'HAYMARKET': '2000',
+    'MILLERS POINT': '2000',
+    'PARLIAMENT HOUSE': '2000',
+    'SYDNEY': '2000',
+    'SYDNEY SOUTH': '2000',
+    'THE ROCKS': '2000',
+    'THE UNIVERSITY OF SYDNEY': '2006',
+    'BROADWAY': '2007',
+    'ULTIMO': '2007',
+    'CHIPPENDALE': '2008',
+    'DARLINGTON': '2008',
+    'PYRMONT': '2009',
+    'DARLINGHURST': '2010',
+    'SURRY HILLS': '2010',
+    'ELIZABETH BAY': '2011',
+    'HMAS KUTTABUL': '2011',
+    'POTTS POINT': '2011',
+    'RUSHCUTTERS BAY': '2011',
+    'WOOLLOOMOOLOO': '2011',
+    'ALEXANDRIA': '2015',
+    'BEACONSFIELD': '2015',
+    'EVELEIGH': '2015',
+    'REDFERN': '2016',
+    'WATERLOO': '2017',
+    'ZETLAND': '2017',
+    'EASTLAKES': '2018',
+    'BANKSMEADOW': '2019',
+    'BOTANY': '2019',
+    'MASCOT': '2020',
+    'SYDNEY DOMESTIC AIRPORT': '2020',
+    'SYDNEY INTERNATIONAL AIRPORT': '2020',
+    'CENTENNIAL PARK': '2021',
+    'MOORE PARK': '2021',
+    'PADDINGTON': '2021',
+    'BONDI JUNCTION': '2022',
+    'BONDI JUNCTION PLAZA': '2022',
+    'QUEENS PARK': '2022',
+    'BELLEVUE HILL': '2023',
+    'BRONTE': '2024',
+    'WAVERLEY': '2024',
+    'WOOLLAHRA': '2025',
+    'BONDI': '2026',
+    'BONDI BEACH': '2026',
+    'NORTH BONDI': '2026',
+    'TAMARAMA': '2026',
+    'DARLING POINT': '2027',
+    'EDGECLIFF': '2027',
+    'HMAS RUSHCUTTERS': '2027',
+    'POINT PIPER': '2027',
+    'DOUBLE BAY': '2028',
+    'ROSE BAY': '2029',
+    'DOVER HEIGHTS': '2030',
+    'HMAS WATSON': '2030',
+    'ROSE BAY NORTH': '2030',
+    'VAUCLUSE': '2030',
+    'WATSONS BAY': '2030',
+    'CLOVELLY': '2031',
+    'CLOVELLY WEST': '2031',
+    'RANDWICK': '2031',
+    'ST PAULS': '2031',
+    'DACEYVILLE': '2032',
+    'KINGSFORD': '2032',
+    'KENSINGTON': '2033',
+    'COOGEE': '2034',
+    'SOUTH COOGEE': '2034',
+    'MAROUBRA': '2035',
+    'MAROUBRA SOUTH': '2035',
+    'PAGEWOOD': '2035',
+    'CHIFLEY': '2036',
+    'EASTGARDENS': '2036',
+    'HILLSDALE': '2036',
+    'LA PEROUSE': '2036',
+    'LITTLE BAY': '2036',
+    'MALABAR': '2036',
+    'MATRAVILLE': '2036',
+    'PHILLIP BAY': '2036',
+    'PORT BOTANY': '2036',
+    'FOREST LODGE': '2037',
+    'GLEBE': '2037',
+    'ANNANDALE': '2038',
+    'ROZELLE': '2039',
+    'LEICHHARDT': '2040',
+    'LILYFIELD': '2040',
+    'BALMAIN': '2041',
+    'BALMAIN EAST': '2041',
+    'BIRCHGROVE': '2041',
+    'ENMORE': '2042',
+    'NEWTOWN': '2042',
+    'ERSKINEVILLE': '2043',
+    'ST PETERS': '2044',
+    'SYDENHAM': '2044',
+    'TEMPE': '2044',
+    'HABERFIELD': '2045',
+    'ABBOTSFORD': '2046',
+    'CANADA BAY': '2046',
+    'CHISWICK': '2046',
+    'FIVE DOCK': '2046',
+    'RODD POINT': '2046',
+    'RUSSELL LEA': '2046',
+    'WAREEMBA': '2046',
+    'DRUMMOYNE': '2047',
+    'STANMORE': '2048',
+    'WESTGATE': '2048',
+    'LEWISHAM': '2049',
+    'PETERSHAM': '2049',
+    'PETERSHAM NORTH': '2049',
+    'CAMPERDOWN': '2050',
+    'MISSENDEN ROAD': '2050',
+    'NORTH SYDNEY': '2055',
+    'HMAS PLATYPUS': '2060',
+    'HMAS WATERHEN': '2060',
+    'LAVENDER BAY': '2060',
+    'MCMAHONS POINT': '2060',
+    'NORTH SYDNEY SHOPPINGWORLD': '2060',
+    'WAVERTON': '2060',
+    'KIRRIBILLI': '2061',
+    'MILSONS POINT': '2061',
+    'CAMMERAY': '2062',
+    'NORTHBRIDGE': '2063',
+    'ARTARMON': '2064',
+    'CROWS NEST': '2065',
+    'GREENWICH': '2065',
+    'NAREMBURN': '2065',
+    'ROYAL NORTH SHORE HOSPITAL': '2065',
+    'ST LEONARDS': '2065',
+    'WOLLSTONECRAFT': '2065',
+    'LANE COVE': '2066',
+    'LANE COVE NORTH': '2066',
+    'LANE COVE WEST': '2066',
+    'LINLEY POINT': '2066',
+    'LONGUEVILLE': '2066',
+    'NORTHWOOD': '2066',
+    'RIVERVIEW': '2066',
+    'CHATSWOOD': '2067',
+    'CHATSWOOD WEST': '2067',
+    'CASTLECRAG': '2068',
+    'MIDDLE COVE': '2068',
+    'NORTH WILLOUGHBY': '2068',
+    'WILLOUGHBY': '2068',
+    'WILLOUGHBY EAST': '2068',
+    'WILLOUGHBY NORTH': '2068',
+    'CASTLE COVE': '2069',
+    'ROSEVILLE': '2069',
+    'ROSEVILLE CHASE': '2069',
+    'EAST LINDFIELD': '2070',
+    'LINDFIELD': '2070',
+    'LINDFIELD WEST': '2070',
+    'EAST KILLARA': '2071',
+    'KILLARA': '2071',
+    'GORDON': '2072',
+    'PYMBLE': '2073',
+    'WEST PYMBLE': '2073',
+    'NORTH TURRAMURRA': '2074',
+    'SOUTH TURRAMURRA': '2074',
+    'TURRAMURRA': '2074',
+    'WARRAWEE': '2074',
+    'ST IVES': '2075',
+    'ST IVES CHASE': '2075',
+    'NORMANHURST': '2076',
+    'NORTH WAHROONGA': '2076',
+    'WAHROONGA': '2076',
+    'ASQUITH': '2077',
+    'HORNSBY': '2077',
+    'HORNSBY HEIGHTS': '2077',
+    'WAITARA': '2077',
+    'MOUNT COLAH': '2079',
+    'MOUNT KURINGGAI': '2080',
+    'BEROWRA': '2081',
+    'COWAN': '2081',
+    'BEROWRA CREEK': '2082',
+    'BEROWRA HEIGHTS': '2082',
+    'BEROWRA WATERS': '2082',
+    'BAR POINT': '2083',
+    'BROOKLYN': '2083',
+    'CHEERO POINT': '2083',
+    'COGRA BAY': '2083',
+    'DANGAR ISLAND': '2083',
+    'MILSONS PASSAGE': '2083',
+    'MOONEY MOONEY': '2083',
+    'COTTAGE POINT': '2084',
+    'DUFFYS FOREST': '2084',
+    'TERREY HILLS': '2084',
+    'BELROSE': '2085',
+    'BELROSE WEST': '2085',
+    'DAVIDSON': '2085',
+    'FRENCHS FOREST': '2086',
+    'FRENCHS FOREST EAST': '2086',
+    'FORESTVILLE': '2087',
+    'KILLARNEY HEIGHTS': '2087',
+    'MOSMAN': '2088',
+    'SPIT JUNCTION': '2088',
+    'NEUTRAL BAY': '2089',
+    'NEUTRAL BAY JUNCTION': '2089',
+    'CREMORNE': '2090',
+    'CREMORNE JUNCTION': '2090',
+    'CREMORNE POINT': '2090',
+    'SEAFORTH': '2092',
+    'BALGOWLAH': '2093',
+    'BALGOWLAH HEIGHTS': '2093',
+    'CLONTARF': '2093',
+    'MANLY VALE': '2093',
+    'NORTH BALGOWLAH': '2093',
+    'FAIRLIGHT': '2094',
+    'MANLY': '2095',
+    'MANLY EAST': '2095',
+    'CURL CURL': '2096',
+    'FRESHWATER': '2096',
+    'HARBORD': '2096',
+    'QUEENSCLIFF': '2096',
+    'COLLAROY': '2097',
+    'COLLAROY BEACH': '2097',
+    'COLLAROY PLATEAU WEST': '2097',
+    'WHEELER HEIGHTS': '2097',
+    'CROMER': '2099',
+    'DEE WHY': '2099',
+    'NARRAWEENA': '2099',
+    'NORTH CURL CURL': '2099',
+    'ALLAMBIE HEIGHTS': '2100',
+    'BEACON HILL': '2100',
+    'BROOKVALE': '2100',
+    'NORTH MANLY': '2100',
+    'OXFORD FALLS': '2100',
+    'WARRINGAH MALL': '2100',
+    'ELANORA HEIGHTS': '2101',
+    'INGLESIDE': '2101',
+    'NARRABEEN': '2101',
+    'NORTH NARRABEEN': '2101',
+    'WARRIEWOOD': '2102',
+    'WARRIEWOOD SHOPPING SQUARE': '2102',
+    'MONA VALE': '2103',
+    'CHURCH POINT': '2105',
+    'ELVINA BAY': '2105',
+    'LOVETT BAY': '2105',
+    'MORNING BAY': '2105',
+    'SCOTLAND ISLAND': '2105',
+    'NEWPORT': '2106',
+    'NEWPORT BEACH': '2106',
+    'AVALON': '2107',
+    'AVALON BEACH': '2107',
+    'BILGOLA': '2107',
+    'CAREEL BAY': '2107',
+    'CLAREVILLE': '2107',
+    'PARADISE BEACH': '2107',
+    'TAYLORS POINT': '2107',
+    'WHALE BEACH': '2107',
+    'COASTERS RETREAT': '2108',
+    'CURRAWONG BEACH': '2108',
+    'GREAT MACKEREL BEACH': '2108',
+    'PALM BEACH': '2108',
+    'THE BASIN': '2108',
+    'HUNTERS HILL': '2110',
+    'WOOLWICH': '2110',
+    'BORONIA PARK': '2111',
+    'GLADESVILLE': '2111',
+    'HENLEY': '2111',
+    'HUNTLEYS COVE': '2111',
+    'HUNTLEYS POINT': '2111',
+    'MONASH PARK': '2111',
+    'TENNYSON POINT': '2111',
+    'DENISTONE EAST': '2112',
+    'PUTNEY': '2112',
+    'RYDE': '2112',
+    'BLENHEIM ROAD': '2113',
+    'EAST RYDE': '2113',
+    'MACQUARIE CENTRE': '2113',
+    'MACQUARIE PARK': '2113',
+    'NORTH RYDE': '2113',
+    'DENISTONE': '2114',
+    'DENISTONE WEST': '2114',
+    'MEADOWBANK': '2114',
+    'MELROSE PARK': '2114',
+    'WEST RYDE': '2114',
+    'ERMINGTON': '2115',
+    'RYDALMERE': '2116',
+    'DUNDAS': '2117',
+    'DUNDAS VALLEY': '2117',
+    'OATLANDS': '2117',
+    'TELOPEA': '2117',
+    'CARLINGFORD': '2118',
+    'CARLINGFORD COURT': '2118',
+    'CARLINGFORD NORTH': '2118',
+    'KINGSDENE': '2118',
+    'BEECROFT': '2119',
+    'CHELTENHAM': '2119',
+    'PENNANT HILLS': '2120',
+    'THORNLEIGH': '2120',
+    'WESTLEIGH': '2120',
+    'EPPING': '2121',
+    'NORTH EPPING': '2121',
+    'EASTWOOD': '2122',
+    'MARSFIELD': '2122',
+    'WEST PENNANT HILLS': '2125',
+    'CHERRYBROOK': '2126',
+    'HOMEBUSH BAY': '2127',
+    'NEWINGTON': '2127',
+    'SILVERWATER': '2128',
+    'SUMMER HILL': '2130',
+    'ASHFIELD': '2131',
+    'CROYDON': '2132',
+    'CROYDON PARK': '2133',
+    'ENFIELD SOUTH': '2133',
+    'BURWOOD': '2134',
+    'BURWOOD NORTH': '2134',
+    'STRATHFIELD': '2135',
+    'BURWOOD HEIGHTS': '2136',
+    'ENFIELD': '2136',
+    'STRATHFIELD SOUTH': '2136',
+    'BREAKFAST POINT': '2137',
+    'CABARITA': '2137',
+    'CONCORD': '2137',
+    'MORTLAKE': '2137',
+    'NORTH STRATHFIELD': '2137',
+    'CONCORD WEST': '2138',
+    'LIBERTY GROVE': '2138',
+    'RHODES': '2138',
+    'HOMEBUSH': '2140',
+    'HOMEBUSH SOUTH': '2140',
+    'HOMEBUSH WEST': '2140',
+    'BERALA': '2141',
+    'LIDCOMBE': '2141',
+    'LIDCOMBE NORTH': '2141',
+    'ROOKWOOD': '2141',
+    'BLAXCELL': '2142',
+    'CAMELLIA': '2142',
+    'CLYDE': '2142',
+    'GRANVILLE': '2142',
+    'HOLROYD': '2142',
+    'ROSEHILL': '2142',
+    'SOUTH GRANVILLE': '2142',
+    'BIRRONG': '2143',
+    'POTTS HILL': '2143',
+    'REGENTS PARK': '2143',
+    'AUBURN': '2144',
+    'CONSTITUTION HILL': '2145',
+    'GREYSTANES': '2145',
+    'MAYS HILL': '2145',
+    'PEMULWUY': '2145',
+    'PENDLE HILL': '2145',
+    'SOUTH WENTWORTHVILLE': '2145',
+    'WENTWORTHVILLE': '2145',
+    'WESTMEAD': '2145',
+    'OLD TOONGABBIE': '2146',
+    'TOONGABBIE': '2146',
+    'TOONGABBIE EAST': '2146',
+    'KINGS LANGLEY': '2147',
+    'LALOR PARK': '2147',
+    'SEVEN HILLS': '2147',
+    'SEVEN HILLS WEST': '2147',
+    'ARNDELL PARK': '2148',
+    'BLACKTOWN': '2148',
+    'BLACKTOWN WESTPOINT': '2148',
+    'HUNTINGWOOD': '2148',
+    'KINGS PARK': '2148',
+    'MARAYONG': '2148',
+    'PROSPECT': '2148',
+    'HARRIS PARK': '2150',
+    'PARRAMATTA': '2150',
+    'PARRAMATTA WESTFIELD': '2150',
+    'NORTH PARRAMATTA': '2151',
+    'NORTH ROCKS': '2151',
+    'NORTHMEAD': '2152',
+    'BAULKHAM HILLS': '2153',
+    'BELLA VISTA': '2153',
+    'WINSTON HILLS': '2153',
+    'CASTLE HILL': '2154',
+    'BEAUMONT HILLS': '2155',
+    'KELLYVILLE': '2155',
+    'KELLYVILLE RIDGE': '2155',
+    'ROUSE HILL': '2155',
+    'ANNANGROVE': '2156',
+    'GLENHAVEN': '2156',
+    'KENTHURST': '2156',
+    'CANOELANDS': '2157',
+    'FOREST GLEN': '2157',
+    'GLENORIE': '2157',
+    'DURAL': '2158',
+    'MIDDLE DURAL': '2158',
+    'ROUND CORNER': '2158',
+    'ARCADIA': '2159',
+    'BERRILEE': '2159',
+    'FIDDLETOWN': '2159',
+    'GALSTON': '2159',
+    'MERRYLANDS': '2160',
+    'MERRYLANDS WEST': '2160',
+    'GUILDFORD': '2161',
+    'GUILDFORD WEST': '2161',
+    'OLD GUILDFORD': '2161',
+    'YENNORA': '2161',
+    'CHESTER HILL': '2162',
+    'SEFTON': '2162',
+    'CARRAMAR': '2163',
+    'VILLAWOOD': '2163',
+    'SMITHFIELD': '2164',
+    'SMITHFIELD WEST': '2164',
+    'WETHERILL PARK': '2164',
+    'WOODPARK': '2164',
+    'FAIRFIELD': '2165',
+    'FAIRFIELD EAST': '2165',
+    'FAIRFIELD HEIGHTS': '2165',
+    'FAIRFIELD WEST': '2165',
+    'CABRAMATTA': '2166',
+    'CABRAMATTA WEST': '2166',
+    'CANLEY HEIGHTS': '2166',
+    'CANLEY VALE': '2166',
+    'LANSVALE': '2166',
+    'GLENFIELD': '2167',
+    'ASHCROFT': '2168',
+    'BUSBY': '2168',
+    'CARTWRIGHT': '2168',
+    'GREEN VALLEY': '2168',
+    'HECKENBERG': '2168',
+    'HINCHINBROOK': '2168',
+    'MILLER': '2168',
+    'SADLEIR': '2168',
+    'CASULA': '2170',
+    'CASULA MALL': '2170',
+    'CHIPPING NORTON': '2170',
+    'HAMMONDVILLE': '2170',
+    'LIVERPOOL': '2170',
+    'LIVERPOOL SOUTH': '2170',
+    'LIVERPOOL WESTFIELD': '2170',
+    'LURNEA': '2170',
+    'MOOREBANK': '2170',
+    'MOUNT PRITCHARD': '2170',
+    'PRESTONS': '2170',
+    'WARWICK FARM': '2170',
+    'CECIL HILLS': '2171',
+    'HORNINGSEA PARK': '2171',
+    'HOXTON PARK': '2171',
+    'MIDDLETON GRANGE': '2171',
+    'WEST HOXTON': '2171',
+    'PLEASURE POINT': '2172',
+    'SANDY POINT': '2172',
+    'VOYAGER POINT': '2172',
+    'HOLSWORTHY': '2173',
+    'WATTLE GROVE': '2173',
+    'EDMONDSON PARK': '2174',
+    'INGLEBURN MILPO': '2174',
+    'HORSLEY PARK': '2175',
+    'ABBOTSBURY': '2176',
+    'BOSSLEY PARK': '2176',
+    'EDENSOR PARK': '2176',
+    'GREENFIELD PARK': '2176',
+    'PRAIRIEWOOD': '2176',
+    'ST JOHNS PARK': '2176',
+    'WAKELEY': '2176',
+    'BONNYRIGG': '2177',
+    'BONNYRIGG HEIGHTS': '2177',
+    'CECIL PARK': '2178',
+    'KEMPS CREEK': '2178',
+    'MOUNT VERNON': '2178',
+    'AUSTRAL': '2179',
+    'LEPPINGTON': '2179',
+    'CHULLORA': '2190',
+    'GREENACRE': '2190',
+    'MOUNT LEWIS': '2190',
+    'BELFIELD': '2191',
+    'BELMORE': '2192',
+    'ASHBURY': '2193',
+    'CANTERBURY': '2193',
+    'HURLSTONE PARK': '2193',
+    'CAMPSIE': '2194',
+    'LAKEMBA': '2195',
+    'WILEY PARK': '2195',
+    'PUNCHBOWL': '2196',
+    'ROSELANDS': '2196',
+    'BASS HILL': '2197',
+    'GEORGES HALL': '2198',
+    'YAGOONA': '2199',
+    'YAGOONA WEST': '2199',
+    'BANKSTOWN': '2200',
+    'BANKSTOWN AERODROME': '2200',
+    'BANKSTOWN NORTH': '2200',
+    'BANKSTOWN SQUARE': '2200',
+    'CONDELL PARK': '2200',
+    'MANAHAN': '2200',
+    'DULWICH HILL': '2203',
+    'MARRICKVILLE': '2204',
+    'MARRICKVILLE METRO': '2204',
+    'MARRICKVILLE SOUTH': '2204',
+    'ARNCLIFFE': '2205',
+    'TURRELLA': '2205',
+    'WOLLI CREEK': '2205',
+    'CLEMTON PARK': '2206',
+    'EARLWOOD': '2206',
+    'BARDWELL PARK': '2207',
+    'BARDWELL VALLEY': '2207',
+    'BEXLEY': '2207',
+    'BEXLEY NORTH': '2207',
+    'BEXLEY SOUTH': '2207',
+    'KINGSGROVE': '2208',
+    'KINGSWAY WEST': '2208',
+    'BEVERLY HILLS': '2209',
+    'NARWEE': '2209',
+    'LUGARNO': '2210',
+    'PEAKHURST': '2210',
+    'PEAKHURST HEIGHTS': '2210',
+    'RIVERWOOD': '2210',
+    'PADSTOW': '2211',
+    'PADSTOW HEIGHTS': '2211',
+    'REVESBY': '2212',
+    'REVESBY HEIGHTS': '2212',
+    'REVESBY NORTH': '2212',
+    'EAST HILLS': '2213',
+    'PANANIA': '2213',
+    'PICNIC POINT': '2213',
+    'MILPERRA': '2214',
+    'BANKSIA': '2216',
+    'BRIGHTONLESANDS': '2216',
+    'KYEEMAGH': '2216',
+    'ROCKDALE': '2216',
+    'BEVERLEY PARK': '2217',
+    'KOGARAH': '2217',
+    'KOGARAH BAY': '2217',
+    'MONTEREY': '2217',
+    'RAMSGATE': '2217',
+    'RAMSGATE BEACH': '2217',
+    'ALLAWAH': '2218',
+    'CARLTON': '2218',
+    'DOLLS POINT': '2219',
+    'SANDRINGHAM': '2219',
+    'SANS SOUCI': '2219',
+    'HURSTVILLE': '2220',
+    'HURSTVILLE GROVE': '2220',
+    'HURSTVILLE WESTFIELD': '2220',
+    'BLAKEHURST': '2221',
+    'CARSS PARK': '2221',
+    'CONNELLS POINT': '2221',
+    'KYLE BAY': '2221',
+    'SOUTH HURSTVILLE': '2221',
+    'PENSHURST': '2222',
+    'MORTDALE': '2223',
+    'OATLEY': '2223',
+    'KANGAROO POINT': '2224',
+    'SYLVANIA': '2224',
+    'SYLVANIA SOUTHGATE': '2224',
+    'SYLVANIA WATERS': '2224',
+    'CARAVAN HEAD': '2225',
+    'OYSTER BAY': '2225',
+    'BONNET BAY': '2226',
+    'COMO': '2226',
+    'JANNALI': '2226',
+    'GYMEA': '2227',
+    'GYMEA BAY': '2227',
+    'MIRANDA': '2228',
+    'YOWIE BAY': '2228',
+    'CARINGBAH': '2229',
+    'DOLANS BAY': '2229',
+    'LILLI PILLI': '2229',
+    'PORT HACKING': '2229',
+    'TAREN POINT': '2229',
+    'WARUMBUL': '2229',
+    'BUNDEENA': '2230',
+    'BURRANEER': '2230',
+    'CRONULLA': '2230',
+    'MAIANBAR': '2230',
+    'WOOLOOWARE': '2230',
+    'KURNELL': '2231',
+    'AUDLEY': '2232',
+    'GARIE': '2232',
+    'GRAYS POINT': '2232',
+    'KAREELA': '2232',
+    'KIRRAWEE': '2232',
+    'LOFTUS': '2232',
+    'SUTHERLAND': '2232',
+    'WORONORA': '2232',
+    'ENGADINE': '2233',
+    'HEATHCOTE': '2233',
+    'WATERFALL': '2233',
+    'WORONORA HEIGHTS': '2233',
+    'YARRAWARRAH': '2233',
+    'ALFORDS POINT': '2234',
+    'BANGOR': '2234',
+    'BARDEN RIDGE': '2234',
+    'ILLAWONG': '2234',
+    'LUCAS HEIGHTS': '2234',
+    'MENAI': '2234',
+    'MENAI CENTRAL': '2234',
+    'BUCKETTY': '2250',
+    'CALGA': '2250',
+    'CENTRAL MANGROVE': '2250',
+    'EAST GOSFORD': '2250',
+    'ERINA': '2250',
+    'ERINA FAIR': '2250',
+    'GLENWORTH VALLEY': '2250',
+    'GOSFORD': '2250',
+    'GREENGROVE': '2250',
+    'HOLGATE': '2250',
+    'KARIONG': '2250',
+    'KULNURA': '2250',
+    'LISAROW': '2250',
+    'LOWER MANGROVE': '2250',
+    'MANGROVE CREEK': '2250',
+    'MANGROVE MOUNTAIN': '2250',
+    'MATCHAM': '2250',
+    'MOONEY MOONEY CREEK': '2250',
+    'MOUNT ELLIOT': '2250',
+    'MOUNT WHITE': '2250',
+    'NARARA': '2250',
+    'NIAGARA PARK': '2250',
+    'NORTH GOSFORD': '2250',
+    'PEATS RIDGE': '2250',
+    'POINT CLARE': '2250',
+    'POINT FREDERICK': '2250',
+    'SOMERSBY': '2250',
+    'SPRINGFIELD': '2250',
+    'TASCOTT': '2250',
+    'TEN MILE HOLLOW': '2250',
+    'UPPER MANGROVE': '2250',
+    'WENDOREE PARK': '2250',
+    'WEST GOSFORD': '2250',
+    'WYOMING': '2250',
+    'AVOCA BEACH': '2251',
+    'BENSVILLE': '2251',
+    'BOUDDI': '2251',
+    'COPACABANA': '2251',
+    'DAVISTOWN': '2251',
+    'GREEN POINT': '2251',
+    'KINCUMBER': '2251',
+    'KINCUMBER SOUTH': '2251',
+    'MACMASTERS BEACH': '2251',
+    'PICKETTS VALLEY': '2251',
+    'SARATOGA': '2251',
+    'YATTALUNGA': '2251',
+    'BLACKWALL': '2256',
+    'HORSFIELD BAY': '2256',
+    'KOOLEWONG': '2256',
+    'LITTLE WOBBY': '2256',
+    'PATONGA': '2256',
+    'PEARL BEACH': '2256',
+    'PHEGANS BAY': '2256',
+    'WONDABYNE': '2256',
+    'WOY WOY': '2256',
+    'WOY WOY BAY': '2256',
+    'BOOKER BAY': '2257',
+    'BOX HEAD': '2257',
+    'DALEYS POINT': '2257',
+    'EMPIRE BAY': '2257',
+    'ETTALONG BEACH': '2257',
+    'HARDYS BAY': '2257',
+    'KILLCARE': '2257',
+    'KILLCARE HEIGHTS': '2257',
+    'PRETTY BEACH': '2257',
+    'ST HUBERTS ISLAND': '2257',
+    'UMINA BEACH': '2257',
+    'WAGSTAFFE': '2257',
+    'FOUNTAINDALE': '2258',
+    'KANGY ANGY': '2258',
+    'OURIMBAH': '2258',
+    'PALM GROVE': '2258',
+    'PALMDALE': '2258',
+    'ALISON': '2259',
+    'BUSHELLS RIDGE': '2259',
+    'CEDAR BRUSH CREEK': '2259',
+    'CHAIN VALLEY BAY': '2259',
+    'CRANGAN BAY': '2259',
+    'DOORALONG': '2259',
+    'DURREN DURREN': '2259',
+    'FRAZER PARK': '2259',
+    'FREEMANS': '2259',
+    'GWANDALAN': '2259',
+    'HALLORAN': '2259',
+    'HAMLYN TERRACE': '2259',
+    'JILLIBY': '2259',
+    'KANWAL': '2259',
+    'KIAR': '2259',
+    'KINGFISHER SHORES': '2259',
+    'LAKE MUNMORAH': '2259',
+    'LEMON TREE': '2259',
+    'LITTLE JILLIBY': '2259',
+    'MANNERING PARK': '2259',
+    'MARDI': '2259',
+    'MOONEE': '2259',
+    'POINT WOLSTONCROFT': '2259',
+    'RAVENSDALE': '2259',
+    'ROCKY POINT': '2259',
+    'SUMMERLAND POINT': '2259',
+    'TACOMA': '2259',
+    'TACOMA SOUTH': '2259',
+    'TUGGERAH': '2259',
+    'TUGGERAWONG': '2259',
+    'WADALBA': '2259',
+    'WALLARAH': '2259',
+    'WARNERVALE': '2259',
+    'WATANOBBI': '2259',
+    'WOONGARRAH': '2259',
+    'WYBUNG': '2259',
+    'WYEE': '2259',
+    'WYEE POINT': '2259',
+    'WYONG': '2259',
+    'WYONG CREEK': '2259',
+    'WYONGAH': '2259',
+    'YARRAMALONG': '2259',
+    'ERINA HEIGHTS': '2260',
+    'FORRESTERS BEACH': '2260',
+    'NORTH AVOCA': '2260',
+    'TERRIGAL': '2260',
+    'WAMBERAL': '2260',
+    'BATEAU BAY': '2261',
+    'BAY VILLAGE': '2261',
+    'BERKELEY VALE': '2261',
+    'BLUE BAY': '2261',
+    'CHITTAWAY BAY': '2261',
+    'CHITTAWAY POINT': '2261',
+    'GLENNING VALLEY': '2261',
+    'KILLARNEY VALE': '2261',
+    'LONG JETTY': '2261',
+    'MAGENTA': '2261',
+    'SHELLY BEACH': '2261',
+    'THE ENTRANCE': '2261',
+    'THE ENTRANCE NORTH': '2261',
+    'TOOWOON BAY': '2261',
+    'TUMBI UMBI': '2261',
+    'BLUE HAVEN': '2262',
+    'BUDGEWOI': '2262',
+    'BUDGEWOI PENINSULA': '2262',
+    'BUFF POINT': '2262',
+    'COLONGRA': '2262',
+    'DOYALSON': '2262',
+    'DOYALSON NORTH': '2262',
+    'HALEKULANI': '2262',
+    'SAN REMO': '2262',
+    'CANTON BEACH': '2263',
+    'CHARMHAVEN': '2263',
+    'GOROKAN': '2263',
+    'LAKE HAVEN': '2263',
+    'NORAH HEAD': '2263',
+    'NORAVILLE': '2263',
+    'TOUKLEY': '2263',
+    'BALCOLYN': '2264',
+    'BONNELLS BAY': '2264',
+    'BRIGHTWATERS': '2264',
+    'DORA CREEK': '2264',
+    'ERARING': '2264',
+    'MANDALONG': '2264',
+    'MIRRABOOKA': '2264',
+    'MORISSET': '2264',
+    'MORISSET PARK': '2264',
+    'MYUNA BAY': '2264',
+    'SUNSHINE': '2264',
+    'WINDERMERE PARK': '2264',
+    'YARRAWONGA PARK': '2264',
+    'COORANBONG': '2265',
+    'MARTINSVILLE': '2265',
+    'WANGI WANGI': '2267',
+    'BARNSLEY': '2278',
+    'KILLINGWORTH': '2278',
+    'WAKEFIELD': '2278',
+    'BELMONT': '2280',
+    'BELMONT NORTH': '2280',
+    'BELMONT SOUTH': '2280',
+    'CROUDACE BAY': '2280',
+    'FLORAVILLE': '2280',
+    'JEWELLS': '2280',
+    'MARKS POINT': '2280',
+    'VALENTINE': '2280',
+    'BLACKSMITHS': '2281',
+    'CAMS WHARF': '2281',
+    'CATHERINE HILL BAY': '2281',
+    'CAVES BEACH': '2281',
+    'LITTLE PELICAN': '2281',
+    'MIDDLE CAMP': '2281',
+    'MURRAYS BEACH': '2281',
+    'NORDS WHARF': '2281',
+    'PELICAN': '2281',
+    'PINNY BEACH': '2281',
+    'SWANSEA': '2281',
+    'SWANSEA HEADS': '2281',
+    'ELEEBANA': '2282',
+    'LAKELANDS': '2282',
+    'WARNERS BAY': '2282',
+    'ARCADIA VALE': '2283',
+    'AWABA': '2283',
+    'BALMORAL': '2283',
+    'BLACKALLS PARK': '2283',
+    'BOLTON POINT': '2283',
+    'BUTTABA': '2283',
+    'CAREY BAY': '2283',
+    'COAL POINT': '2283',
+    'FASSIFERN': '2283',
+    'FENNELL BAY': '2283',
+    'FISHING POINT': '2283',
+    'KILABEN BAY': '2283',
+    'RATHMINES': '2283',
+    'RYHOPE': '2283',
+    'TORONTO': '2283',
+    'ARGENTON': '2284',
+    'BOOLAROO': '2284',
+    'BOORAGUL': '2284',
+    'MARMONG POINT': '2284',
+    'SPEERS POINT': '2284',
+    'TERALBA': '2284',
+    'WOODRISING': '2284',
+    'CAMERON PARK': '2285',
+    'CARDIFF': '2285',
+    'CARDIFF HEIGHTS': '2285',
+    'CARDIFF SOUTH': '2285',
+    'EDGEWORTH': '2285',
+    'GLENDALE': '2285',
+    'MACQUARIE HILLS': '2285',
+    'HOLMESVILLE': '2286',
+    'SEAHAMPTON': '2286',
+    'WEST WALLSEND': '2286',
+    'BIRMINGHAM GARDENS': '2287',
+    'ELERMORE VALE': '2287',
+    'FLETCHER': '2287',
+    'MARYLAND': '2287',
+    'MINMI': '2287',
+    'RANKIN PARK': '2287',
+    'WALLSEND': '2287',
+    'WALLSEND SOUTH': '2287',
+    'ADAMSTOWN': '2289',
+    'ADAMSTOWN HEIGHTS': '2289',
+    'GARDEN SUBURB': '2289',
+    'HIGHFIELDS': '2289',
+    'KOTARA': '2289',
+    'KOTARA FAIR': '2289',
+    'KOTARA SOUTH': '2289',
+    'BENNETTS GREEN': '2290',
+    'CHARLESTOWN': '2290',
+    'DUDLEY': '2290',
+    'GATESHEAD': '2290',
+    'HILLSBOROUGH': '2290',
+    'KAHIBAH': '2290',
+    'MOUNT HUTTON': '2290',
+    'REDHEAD': '2290',
+    'TINGIRA HEIGHTS': '2290',
+    'WHITEBRIDGE': '2290',
+    'MEREWETHER': '2291',
+    'MEREWETHER HEIGHTS': '2291',
+    'THE JUNCTION': '2291',
+    'BROADMEADOW': '2292',
+    'HAMILTON NORTH': '2292',
+    'MARYVILLE': '2293',
+    'WICKHAM': '2293',
+    'CARRINGTON': '2294',
+    'FERN BAY': '2295',
+    'STOCKTON': '2295',
+    'ISLINGTON': '2296',
+    'TIGHES HILL': '2297',
+    'GEORGETOWN': '2298',
+    'WARATAH': '2298',
+    'WARATAH WEST': '2298',
+    'JESMOND': '2299',
+    'LAMBTON': '2299',
+    'NORTH LAMBTON': '2299',
+    'BAR BEACH': '2300',
+    'COOKS HILL': '2300',
+    'NEWCASTLE': '2300',
+    'NEWCASTLE EAST': '2300',
+    'THE HILL': '2300',
+    'NEWCASTLE WEST': '2302',
+    'HAMILTON': '2303',
+    'HAMILTON EAST': '2303',
+    'HAMILTON SOUTH': '2303',
+    'KOORAGANG': '2304',
+    'MAYFIELD': '2304',
+    'MAYFIELD EAST': '2304',
+    'MAYFIELD NORTH': '2304',
+    'MAYFIELD WEST': '2304',
+    'SANDGATE': '2304',
+    'WARABROOK': '2304',
+    'KOTARA EAST': '2305',
+    'NEW LAMBTON': '2305',
+    'NEW LAMBTON HEIGHTS': '2305',
+    'WINDALE': '2306',
+    'SHORTLAND': '2307',
+    'CALLAGHAN': '2308',
+    'NEWCASTLE UNIVERSITY': '2308',
+    'DANGAR': '2309',
+    'ALLYNBROOK': '2311',
+    'BINGLEBURRA': '2311',
+    'CARRABOLLA': '2311',
+    'EAST GRESFORD': '2311',
+    'ECCLESTON': '2311',
+    'GRESFORD': '2311',
+    'HALTON': '2311',
+    'LEWINSBROOK': '2311',
+    'LOSTOCK': '2311',
+    'MOUNT RIVERS': '2311',
+    'UPPER ALLYN': '2311',
+    'MINIMBAH': '2312',
+    'NABIAC': '2312',
+    'CORLETTE': '2315',
+    'FINGAL BAY': '2315',
+    'NELSON BAY': '2315',
+    'ANNA BAY': '2316',
+    'BOAT HARBOUR': '2316',
+    'BOBS FARM': '2316',
+    'FISHERMANS BAY': '2316',
+    'ONE MILE': '2316',
+    'TAYLORS BEACH': '2316',
+    'SALAMANDER BAY': '2317',
+    'SOLDIERS POINT': '2317',
+    'CAMPVALE': '2318',
+    'FERODALE': '2318',
+    'FULLERTON COVE': '2318',
+    'MEDOWIE': '2318',
+    'OYSTER COVE': '2318',
+    'SALT ASH': '2318',
+    'WILLIAMTOWN': '2318',
+    'LEMON TREE PASSAGE': '2319',
+    'MALLABULA': '2319',
+    'TANILBA BAY': '2319',
+    'TILLIGERRY CREEK': '2319',
+    'ABERGLASSLYN': '2320',
+    'ALLANDALE': '2320',
+    'ANAMBAH': '2320',
+    'BOLWARRA': '2320',
+    'BOLWARRA HEIGHTS': '2320',
+    'FARLEY': '2320',
+    'GLEN OAK': '2320',
+    'GOSFORTH': '2320',
+    'HORSESHOE BEND': '2320',
+    'KEINBAH': '2320',
+    'LARGS': '2320',
+    'LORN': '2320',
+    'LOUTH PARK': '2320',
+    'MAITLAND': '2320',
+    'MAITLAND NORTH': '2320',
+    'MAITLAND VALE': '2320',
+    'MELVILLE': '2320',
+    'MINDARIBBA': '2320',
+    'MOUNT DEE': '2320',
+    'OAKHAMPTON': '2320',
+    'OAKHAMPTON HEIGHTS': '2320',
+    'POKOLBIN': '2320',
+    'ROSEBROOK': '2320',
+    'ROTHBURY': '2320',
+    'RUTHERFORD': '2320',
+    'SOUTH MAITLAND': '2320',
+    'TELARAH': '2320',
+    'WALLALONG': '2320',
+    'WINDELLA': '2320',
+    'BERRY PARK': '2321',
+    'BUTTERWICK': '2321',
+    'CLARENCE TOWN': '2321',
+    'CLIFTLEIGH': '2321',
+    'DUCKENFIELD': '2321',
+    'DUNS CREEK': '2321',
+    'GILLIESTON HEIGHTS': '2321',
+    'GLEN MARTIN': '2321',
+    'GLEN WILLIAM': '2321',
+    'HARPERS HILL': '2321',
+    'HEDDON GRETA': '2321',
+    'HINTON': '2321',
+    'LOCHINVAR': '2321',
+    'LUSKINTYRE': '2321',
+    'MORPETH': '2321',
+    'OSWALD': '2321',
+    'PHOENIX PARK': '2321',
+    'RAWORTH': '2321',
+    'WINDERMERE': '2321',
+    'WOODVILLE': '2321',
+    'BERESFIELD': '2322',
+    'BLACK HILL': '2322',
+    'CHISHOLM': '2322',
+    'HEXHAM': '2322',
+    'LENAGHAN': '2322',
+    'STOCKRINGTON': '2322',
+    'TARRO': '2322',
+    'THORNTON': '2322',
+    'TOMAGO': '2322',
+    'WOODBERRY': '2322',
+    'ASHTONFIELD': '2323',
+    'BRUNKERVILLE': '2323',
+    'BUTTAI': '2323',
+    'EAST MAITLAND': '2323',
+    'FOUR MILE CREEK': '2323',
+    'FREEMANS WATERHOLE': '2323',
+    'GREEN HILLS': '2323',
+    'METFORD': '2323',
+    'METFORD DC': '2323',
+    'MOUNT VINCENT': '2323',
+    'MULBRING': '2323',
+    'PITNACREE': '2323',
+    'RICHMOND VALE': '2323',
+    'TENAMBIT': '2323',
+    'BALICKERA': '2324',
+    'BRANDY HILL': '2324',
+    'BUNDABAH': '2324',
+    'EAGLETON': '2324',
+    'EAST SEAHAM': '2324',
+    'HAWKS NEST': '2324',
+    'HEATHERBRAE': '2324',
+    'KARUAH': '2324',
+    'LIMEBURNERS CREEK': '2324',
+    'MILLERS FOREST': '2324',
+    'NELSONS PLAINS': '2324',
+    'NORTH ARM COVE': '2324',
+    'OSTERLEY': '2324',
+    'PINDIMAR': '2324',
+    'RAYMOND TERRACE': '2324',
+    'RAYMOND TERRACE EAST': '2324',
+    'SEAHAM': '2324',
+    'SWAN BAY': '2324',
+    'TAHLEE': '2324',
+    'TEA GARDENS': '2324',
+    'TWELVE MILE CREEK': '2324',
+    'ABERDARE': '2325',
+    'ABERNETHY': '2325',
+    'BELLBIRD': '2325',
+    'BELLBIRD HEIGHTS': '2325',
+    'BOREE': '2325',
+    'CEDAR CREEK': '2325',
+    'CESSNOCK': '2325',
+    'CESSNOCK WEST': '2325',
+    'CONGEWAI': '2325',
+    'CORRABARE': '2325',
+    'ELLALONG': '2325',
+    'ELRINGTON': '2325',
+    'GRETA MAIN': '2325',
+    'KEARSLEY': '2325',
+    'KITCHENER': '2325',
+    'LAGUNA': '2325',
+    'LOVEDALE': '2325',
+    'MILLFIELD': '2325',
+    'MORUBEN': '2325',
+    'MOUNT VIEW': '2325',
+    'NULKABA': '2325',
+    'OLNEY': '2325',
+    'PAXTON': '2325',
+    'PAYNES CROSSING': '2325',
+    'PELTON': '2325',
+    'QUORROBOLONG': '2325',
+    'SWEETMANS CREEK': '2325',
+    'WOLLOMBI': '2325',
+    'ABERMAIN': '2326',
+    'BISHOPS BRIDGE': '2326',
+    'LOXFORD': '2326',
+    'NEATH': '2326',
+    'SAWYERS GULLY': '2326',
+    'WESTON': '2326',
+    'KURRI KURRI': '2327',
+    'PELAW MAIN': '2327',
+    'STANFORD MERTHYR': '2327',
+    'BUREEN': '2328',
+    'DALSWINTON': '2328',
+    'DENMAN': '2328',
+    'GIANTS CREEK': '2328',
+    'HOLLYDEEN': '2328',
+    'KERRABEE': '2328',
+    'MANGOOLA': '2328',
+    'MARTINDALE': '2328',
+    'WIDDEN': '2328',
+    'YARRAWA': '2328',
+    'BORAMBIL': '2329',
+    'CASSILIS': '2329',
+    'MERRIWA': '2329',
+    'UARBRY': '2329',
+    'APPLETREE FLAT': '2330',
+    'BIG RIDGE': '2330',
+    'BIG YENGO': '2330',
+    'BOWMANS CREEK': '2330',
+    'BRIDGMAN': '2330',
+    'BROKE': '2330',
+    'BULGA': '2330',
+    'CAMBERWELL': '2330',
+    'CARROWBROOK': '2330',
+    'CLYDESDALE': '2330',
+    'COMBO': '2330',
+    'DOYLES CREEK': '2330',
+    'DUNOLLY': '2330',
+    'DYRRING': '2330',
+    'FALBROOK': '2330',
+    'FERN GULLY': '2330',
+    'FORDWICH': '2330',
+    'GARLAND VALLEY': '2330',
+    'GLENDON': '2330',
+    'GLENDON BROOK': '2330',
+    'GLENNIES CREEK': '2330',
+    'GLENRIDDING': '2330',
+    'GOORANGOOLA': '2330',
+    'GOULDSVILLE': '2330',
+    'GOWRIE': '2330',
+    'GREENLANDS': '2330',
+    'HAMBLEDON HILL': '2330',
+    'HEBDEN': '2330',
+    'HOWES VALLEY': '2330',
+    'HOWICK': '2330',
+    'HUNTERVIEW': '2330',
+    'JERRYS PLAINS': '2330',
+    'LEMINGTON': '2330',
+    'LONG POINT': '2330',
+    'MAISON DIEU': '2330',
+    'MCDOUGALLS HILL': '2330',
+    'MIDDLE FALBROOK': '2330',
+    'MILBRODALE': '2330',
+    'MIRANNIE': '2330',
+    'MITCHELLS FLAT': '2330',
+    'MOUNT OLIVE': '2330',
+    'MOUNT ROYAL': '2330',
+    'MOUNT THORLEY': '2330',
+    'OBANVALE': '2330',
+    'PUTTY': '2330',
+    'RAVENSWORTH': '2330',
+    'REDBOURNBERRY': '2330',
+    'REEDY CREEK': '2330',
+    'RIXS CREEK': '2330',
+    'ROUGHIT': '2330',
+    'SCOTTS FLAT': '2330',
+    'SEDGEFIELD': '2330',
+    'SINGLETON': '2330',
+    'SINGLETON HEIGHTS': '2330',
+    'ST CLAIR': '2330',
+    'WARKWORTH': '2330',
+    'WATTLE PONDS': '2330',
+    'WESTBROOK': '2330',
+    'WHITTINGHAM': '2330',
+    'WOLLEMI': '2330',
+    'WYLIES FLAT': '2330',
+    'BAERAMI': '2333',
+    'BAERAMI CREEK': '2333',
+    'BENGALLA': '2333',
+    'CASTLE ROCK': '2333',
+    'EDDERTON': '2333',
+    'GUNGAL': '2333',
+    'KAYUGA': '2333',
+    'LIDDELL': '2333',
+    'MANOBALAI': '2333',
+    'MCCULLYS GAP': '2333',
+    'MUSCLE CREEK': '2333',
+    'MUSWELLBROOK': '2333',
+    'SANDY HOLLOW': '2333',
+    'WYBONG': '2333',
+    'GRETA': '2334',
+    'BELFORD': '2335',
+    'BRANXTON': '2335',
+    'DALWOOD': '2335',
+    'EAST BRANXTON': '2335',
+    'ELDERSLIE': '2335',
+    'LAMBS VALLEY': '2335',
+    'LECONFIELD': '2335',
+    'LOWER BELFORD': '2335',
+    'NORTH ROTHBURY': '2335',
+    'STANHOPE': '2335',
+    'ABERDEEN': '2336',
+    'DARTBROOK': '2336',
+    'DAVIS CREEK': '2336',
+    'ROSSGOLE': '2336',
+    'ROUCHEL': '2336',
+    'ROUCHEL BROOK': '2336',
+    'UPPER DARTBROOK': '2336',
+    'UPPER ROUCHEL': '2336',
+    'BELLTREES': '2337',
+    'BRAWBOY': '2337',
+    'BUNNAN': '2337',
+    'DRY CREEK': '2337',
+    'ELLERSTON': '2337',
+    'GLENBAWN': '2337',
+    'GLENROCK': '2337',
+    'GUNDY': '2337',
+    'KARS SPRINGS': '2337',
+    'MIDDLE BROOK': '2337',
+    'MOOBI': '2337',
+    'MOONAN BROOK': '2337',
+    'MOONAN FLAT': '2337',
+    'MURULLA': '2337',
+    'OMADALE': '2337',
+    'OWENS GAP': '2337',
+    'PAGES CREEK': '2337',
+    'PARKVILLE': '2337',
+    'SCONE': '2337',
+    'SEGENHOE': '2337',
+    'STEWARTS BROOK': '2337',
+    'TOMALLA': '2337',
+    'WAVERLY': '2337',
+    'WINGEN': '2337',
+    'WOOLOOMA': '2337',
+    'ARDGLEN': '2338',
+    'BLANDFORD': '2338',
+    'CRAWNEY': '2338',
+    'GREEN CREEK': '2338',
+    'MURRURUNDI': '2338',
+    'PAGES RIVER': '2338',
+    'SANDY CREEK': '2338',
+    'SCOTTS CREEK': '2338',
+    'TIMOR': '2338',
+    'BIG JACKS CREEK': '2339',
+    'BRAEFIELD': '2339',
+    'CATTLE CREEK': '2339',
+    'CHILCOTTS CREEK': '2339',
+    'LITTLE JACKS CREEK': '2339',
+    'MACDONALDS CREEK': '2339',
+    'PARRAWEENA': '2339',
+    'WARRAH': '2339',
+    'WARRAH CREEK': '2339',
+    'WILLOW TREE': '2339',
+    'APPLEBY': '2340',
+    'BARRY': '2340',
+    'BECTIVE': '2340',
+    'BITHRAMERE': '2340',
+    'BOWLING ALLEY POINT': '2340',
+    'CALALA': '2340',
+    'CARROLL': '2340',
+    'DARUKA': '2340',
+    'DUNCANS CREEK': '2340',
+    'DUNGOWAN': '2340',
+    'EAST TAMWORTH': '2340',
+    'GAROO': '2340',
+    'GIDLEY': '2340',
+    'GOONOO GOONOO': '2340',
+    'HALLSVILLE': '2340',
+    'HANGING ROCK': '2340',
+    'HILLVUE': '2340',
+    'KEEPIT': '2340',
+    'KINGSWOOD': '2340',
+    'LOOMBERAH': '2340',
+    'MOORE CREEK': '2340',
+    'NEMINGHA': '2340',
+    'NORTH TAMWORTH': '2340',
+    'NUNDLE': '2340',
+    'OGUNBIL': '2340',
+    'OXLEY VALE': '2340',
+    'PIALLAMORE': '2340',
+    'SOMERTON': '2340',
+    'SOUTH TAMWORTH': '2340',
+    'TAMINDA': '2340',
+    'TAMWORTH': '2340',
+    'TIMBUMBURI': '2340',
+    'WALLAMORE': '2340',
+    'WARRAL': '2340',
+    'WEABONGA': '2340',
+    'WEST TAMWORTH': '2340',
+    'WESTDALE': '2340',
+    'WOOLOMIN': '2340',
+    'WERRIS CREEK': '2341',
+    'CURRABUBULA': '2342',
+    'PIALLAWAY': '2342',
+    'BLACKVILLE': '2343',
+    'BUNDELLA': '2343',
+    'CAROONA': '2343',
+    'COLLY BLUE': '2343',
+    'COOMOO COOMOO': '2343',
+    'PINE RIDGE': '2343',
+    'QUIPOLLY': '2343',
+    'QUIRINDI': '2343',
+    'SPRING RIDGE': '2343',
+    'WALLABADAH': '2343',
+    'WARRAH RIDGE': '2343',
+    'WINDY': '2343',
+    'YANNERGEE': '2343',
+    'DURI': '2344',
+    'WINTON': '2344',
+    'ATTUNGA': '2345',
+    'GARTHOWEN': '2345',
+    'BORAH CREEK': '2346',
+    'HALLS CREEK': '2346',
+    'KLORI': '2346',
+    'MANILLA': '2346',
+    'NAMOI RIVER': '2346',
+    'NEW MEXICO': '2346',
+    'RUSHES CREEK': '2346',
+    'UPPER MANILLA': '2346',
+    'WARRABAH': '2346',
+    'WIMBORNE': '2346',
+    'WONGO CREEK': '2346',
+    'BANOON': '2347',
+    'BARRABA': '2347',
+    'CARODA': '2347',
+    'COBBADAH': '2347',
+    'GULF CREEK': '2347',
+    'GUNDAMULDA': '2347',
+    'IRONBARK': '2347',
+    'LINDESAY': '2347',
+    'LONGARM': '2347',
+    'MAYVALE': '2347',
+    'RED HILL': '2347',
+    'THIRLOENE': '2347',
+    'UPPER HORTON': '2347',
+    'WOODSREEF': '2347',
+    'ABERFOYLE': '2350',
+    'ABINGTON': '2350',
+    'ARGYLE': '2350',
+    'ARMIDALE': '2350',
+    'BONA VISTA': '2350',
+    'BOOROLONG': '2350',
+    'CASTLE DOYLE': '2350',
+    'DANGARSLEIGH': '2350',
+    'DONALD CREEK': '2350',
+    'DUMARESQ': '2350',
+    'DUVAL': '2350',
+    'HILLGROVE': '2350',
+    'INVERGOWRIE': '2350',
+    'JEOGLA': '2350',
+    'KELLYS PLAINS': '2350',
+    'LYNDHURST': '2350',
+    'PUDDLEDOCK': '2350',
+    'SAUMAREZ': '2350',
+    'SAUMAREZ PONDS': '2350',
+    'THALGARRAH': '2350',
+    'TILBUSTER': '2350',
+    'WARDS MISTAKE': '2350',
+    'WEST ARMIDALE': '2350',
+    'WOLLOMOMBI': '2350',
+    'WONGWIBINDA': '2350',
+    'KOOTINGAL': '2352',
+    'LIMBRI': '2352',
+    'MULLA CREEK': '2352',
+    'TINTINHULL': '2352',
+    'MOONBI': '2353',
+    'BRANGA PLAINS': '2354',
+    'KENTUCKY': '2354',
+    'KENTUCKY SOUTH': '2354',
+    'MOONA PLAINS': '2354',
+    'NIANGALA': '2354',
+    'NOWENDOC': '2354',
+    'UPPER YARROWITCH': '2354',
+    'WALCHA': '2354',
+    'WALCHA ROAD': '2354',
+    'WOLLUN': '2354',
+    'WOOLBROOK': '2354',
+    'YARROWITCH': '2354',
+    'BENDEMEER': '2355',
+    'RETREAT': '2355',
+    'WATSONS CREEK': '2355',
+    'GWABEGAR': '2356',
+    'BOMERA': '2357',
+    'BUGALDIE': '2357',
+    'COONABARABRAN': '2357',
+    'DANDRY': '2357',
+    'GOWANG': '2357',
+    'PURLEWAUGH': '2357',
+    'ROCKY GLEN': '2357',
+    'TANNABAR': '2357',
+    'ULAMAMBRI': '2357',
+    'ARDING': '2358',
+    'BALALA': '2358',
+    'GOSTWYCK': '2358',
+    'KINGSTOWN': '2358',
+    'MIHI': '2358',
+    'ROCKY RIVER': '2358',
+    'SALISBURY PLAINS': '2358',
+    'TORRYBURN': '2358',
+    'YARROWYCK': '2358',
+    'BAKERS CREEK': '2359',
+    'BUNDARRA': '2359',
+    'CAMERONS CREEK': '2359',
+    'AUBURN VALE': '2360',
+    'BRODIES PLAINS': '2360',
+    'BUKKULLA': '2360',
+    'CHERRY TREE HILL': '2360',
+    'COPETON': '2360',
+    'ELSMORE': '2360',
+    'GILGAI': '2360',
+    'GRAMAN': '2360',
+    'GUM FLAT': '2360',
+    'HOWELL': '2360',
+    'INVERELL': '2360',
+    'KINGS PLAINS': '2360',
+    'LITTLE PLAIN': '2360',
+    'LONG PLAIN': '2360',
+    'MOUNT RUSSELL': '2360',
+    'NEWSTEAD': '2360',
+    'NULLAMANNA': '2360',
+    'OAKWOOD': '2360',
+    'PARADISE': '2360',
+    'ROB ROY': '2360',
+    'SAPPHIRE': '2360',
+    'SPRING MOUNTAIN': '2360',
+    'STANBOROUGH': '2360',
+    'SWANBROOK': '2360',
+    'WALLANGRA': '2360',
+    'WANDERA': '2360',
+    'WOODSTOCK': '2360',
+    'ASHFORD': '2361',
+    'ATHOLWOOD': '2361',
+    'BONSHAW': '2361',
+    'LIMESTONE': '2361',
+    'PINDAROI': '2361',
+    'BACKWATER': '2365',
+    'BALD BLAIR': '2365',
+    'BALDERSLEIGH': '2365',
+    'BASSENDEAN': '2365',
+    'BEN LOMOND': '2365',
+    'BLACK MOUNTAIN': '2365',
+    'BRIARBROOK': '2365',
+    'BROCKLEY': '2365',
+    'BRUSHY CREEK': '2365',
+    'FALCONER': '2365',
+    'GEORGES CREEK': '2365',
+    'GLEN NEVIS': '2365',
+    'GLENCOE': '2365',
+    'GUYRA': '2365',
+    'LLANGOTHLIN': '2365',
+    'MAYBOLE': '2365',
+    'MOUNT MITCHELL': '2365',
+    'NEW VALLEY': '2365',
+    'OBAN': '2365',
+    'SOUTH GUYRA': '2365',
+    'TENTERDEN': '2365',
+    'THE GULF': '2365',
+    'TUBBAMURRA': '2365',
+    'WANDSWORTH': '2365',
+    'OLD MILL': '2369',
+    'STANNIFER': '2369',
+    'TINGHA': '2369',
+    'BALD NOB': '2370',
+    'DIEHARD': '2370',
+    'DUNDEE': '2370',
+    'FURRACABAD': '2370',
+    'GIBRALTAR RANGE': '2370',
+    'GLEN ELGIN': '2370',
+    'GLEN INNES': '2370',
+    'KINGSGATE': '2370',
+    'KOOKABOOKRA': '2370',
+    'MATHESON': '2370',
+    'MOGGS SWAMP': '2370',
+    'MOOGEM': '2370',
+    'MORVEN': '2370',
+    'NEWTON BOYD': '2370',
+    'PINKETT': '2370',
+    'RANGERS VALLEY': '2370',
+    'RED RANGE': '2370',
+    'REDDESTONE': '2370',
+    'SHANNON VALE': '2370',
+    'STONEHENGE': '2370',
+    'SWAN VALE': '2370',
+    'WELLINGROVE': '2370',
+    'YARROWFORD': '2370',
+    'CAPOOMPETA': '2371',
+    'DEEPWATER': '2371',
+    'EMMAVILLE': '2371',
+    'ROCKY CREEK': '2371',
+    'STANNUM': '2371',
+    'TENT HILL': '2371',
+    'TORRINGTON': '2371',
+    'TUNGSTEN': '2371',
+    'WELLINGTON VALE': '2371',
+    'YELLOW DAM': '2371',
+    'BACK CREEK': '2372',
+    'BARNEY DOWNS': '2372',
+    'BILLYRIMBA': '2372',
+    'BLACK SWAMP': '2372',
+    'BLUFF ROCK': '2372',
+    'BOLIVIA': '2372',
+    'BOONOO BOONOO': '2372',
+    'BOOROOK': '2372',
+    'BRYANS GAP': '2372',
+    'BUNGULLA': '2372',
+    'CARROLLS CREEK': '2372',
+    'CULLENDORE': '2372',
+    'FOREST LAND': '2372',
+    'HOMESTEAD': '2372',
+    'JENNINGS': '2372',
+    'LEECHS GULLY': '2372',
+    'LISTON': '2372',
+    'MINGOOLA': '2372',
+    'MOLE RIVER': '2372',
+    'MOUNT MACKENZIE': '2372',
+    'PYES CREEK': '2372',
+    'RIVERTREE': '2372',
+    'SANDY FLAT': '2372',
+    'SANDY HILL': '2372',
+    'SILENT GROVE': '2372',
+    'STEINBROOK': '2372',
+    'SUNNYSIDE': '2372',
+    'TARBAN': '2372',
+    'TENTERFIELD': '2372',
+    'THE SCRUB': '2372',
+    'TIMBARRA': '2372',
+    'WILLSONS DOWNFALL': '2372',
+    'WOODSIDE': '2372',
+    'WYLIE CREEK': '2372',
+    'GOOLHI': '2379',
+    'MULLALEY': '2379',
+    'NAPIER LANE': '2379',
+    'NOMBI': '2379',
+    'BLUE VALE': '2380',
+    'EMERALD HILL': '2380',
+    'GHOOLENDAADI': '2380',
+    'GUNNEDAH': '2380',
+    'KELVIN': '2380',
+    'MARYS MOUNT': '2380',
+    'MILROY': '2380',
+    'ORANGE GROVE': '2380',
+    'RANGARI': '2380',
+    'BREEZA': '2381',
+    'CURLEWIS': '2381',
+    'PREMER': '2381',
+    'TAMBAR SPRINGS': '2381',
+    'BOGGABRI': '2382',
+    'MAULES CREEK': '2382',
+    'WEAN': '2382',
+    'WILLALA': '2382',
+    'BURREN JUNCTION': '2386',
+    'DRILDOOL': '2386',
+    'NOWLEY': '2386',
+    'BULYEROI': '2387',
+    'ROWENA': '2387',
+    'BOOLCARROLL': '2388',
+    'CUTTABRI': '2388',
+    'JEWS LAGOON': '2388',
+    'MERAH NORTH': '2388',
+    'PILLIGA': '2388',
+    'SPRING PLAINS': '2388',
+    'THE PILLIGA': '2388',
+    'WEE WAA': '2388',
+    'YARRIE LAKE': '2388',
+    'BAAN BAA': '2390',
+    'BERRIGAL': '2390',
+    'BOHENA CREEK': '2390',
+    'BULLAWA CREEK': '2390',
+    'COURADDA': '2390',
+    'EDGEROI': '2390',
+    'EULAH CREEK': '2390',
+    'HARPARARY': '2390',
+    'JACKS CREEK': '2390',
+    'KAPUTAR': '2390',
+    'NARRABRI': '2390',
+    'NARRABRI WEST': '2390',
+    'TARRIARO': '2390',
+    'TURRAWAN': '2390',
+    'BINNAWAY': '2395',
+    'ROPERS ROAD': '2395',
+    'WEETALIBA': '2395',
+    'BARADINE': '2396',
+    'BARWON': '2396',
+    'GOORIANAWA': '2396',
+    'KENEBRI': '2396',
+    'BELLATA': '2397',
+    'MILLIE': '2397',
+    'THALABA': '2397',
+    'GURLEY': '2398',
+    'BINIGUY': '2399',
+    'PALLAMALLAWA': '2399',
+    'ASHLEY': '2400',
+    'BULLARAH': '2400',
+    'CROOBLE': '2400',
+    'MALLOWA': '2400',
+    'MOREE': '2400',
+    'MOREE EAST': '2400',
+    'TERRY HIE HIE': '2400',
+    'TULLOONA': '2400',
+    'YARRAMAN': '2400',
+    'GRAVESEND': '2401',
+    'BALFOURS PEAK': '2402',
+    'COOLATAI': '2402',
+    'WARIALDA': '2402',
+    'WARIALDA RAIL': '2402',
+    'DELUNGRA': '2403',
+    'GRAGIN': '2403',
+    'MYALL CREEK': '2403',
+    'BANGHEET': '2404',
+    'BINGARA': '2404',
+    'DINOGA': '2404',
+    'ELCOMBE': '2404',
+    'GINEROI': '2404',
+    'KEERA': '2404',
+    'PALLAL': '2404',
+    'UPPER BINGARA': '2404',
+    'BOOMI': '2405',
+    'GARAH': '2405',
+    'MUNGINDI': '2406',
+    'WEEMELAH': '2406',
+    'NORTH STAR': '2408',
+    'BOGGABILLA': '2409',
+    'BOONAL': '2409',
+    'BLUE NOBBY': '2410',
+    'TWIN RIVERS': '2410',
+    'YETMAN': '2410',
+    'CROPPA CREEK': '2411',
+    'YALLAROI': '2411',
+    'MONKERAI': '2415',
+    'NOOROO': '2415',
+    'STROUD ROAD': '2415',
+    'UPPER KARUAH RIVER': '2415',
+    'WEISMANTELS': '2415',
+    'BANDON GROVE': '2420',
+    'BENDOLBA': '2420',
+    'BROOKFIELD': '2420',
+    'CAMBRA': '2420',
+    'CHICHESTER': '2420',
+    'DUNGOG': '2420',
+    'FLAT TOPS': '2420',
+    'FOSTERTON': '2420',
+    'HANLEYS CREEK': '2420',
+    'HILLDALE': '2420',
+    'MAIN CREEK': '2420',
+    'MARSHDALE': '2420',
+    'MARTINS CREEK': '2420',
+    'MUNNI': '2420',
+    'SALISBURY': '2420',
+    'STROUD HILL': '2420',
+    'SUGARLOAF': '2420',
+    'TABBIL CREEK': '2420',
+    'UNDERBANK': '2420',
+    'WALLARINGA': '2420',
+    'WALLAROBBA': '2420',
+    'WIRRAGULLA': '2420',
+    'FISHERS HILL': '2421',
+    'PATERSON': '2421',
+    'TOCAL': '2421',
+    'VACY': '2421',
+    'WEBBERS CREEK': '2421',
+    'BARRINGTON': '2422',
+    'BARRINGTON TOPS': '2422',
+    'BAXTERS RIDGE': '2422',
+    'BELBORA': '2422',
+    'BERRICO': '2422',
+    'BINDERA': '2422',
+    'BOWMAN': '2422',
+    'BOWMAN FARM': '2422',
+    'BRETTI': '2422',
+    'BULLIAC': '2422',
+    'BUNDOOK': '2422',
+    'CALLAGHANS CREEK': '2422',
+    'COBARK': '2422',
+    'CONEAC': '2422',
+    'COPELAND': '2422',
+    'CRAVEN': '2422',
+    'CRAVEN PLATEAU': '2422',
+    'CURRICABARK': '2422',
+    'DEWITT': '2422',
+    'FAULKLAND': '2422',
+    'FORBESDALE': '2422',
+    'GANGAT': '2422',
+    'GIRO': '2422',
+    'GLEN WARD': '2422',
+    'GLOUCESTER': '2422',
+    'GLOUCESTER TOPS': '2422',
+    'INVERGORDON': '2422',
+    'KIA ORA': '2422',
+    'MARES RUN': '2422',
+    'MERNOT': '2422',
+    'MOGRANI': '2422',
+    'MOPPY': '2422',
+    'RAWDON VALE': '2422',
+    'ROOKHURST': '2422',
+    'STRATFORD': '2422',
+    'TERREEL': '2422',
+    'TIBBUC': '2422',
+    'TITAATEE CREEK': '2422',
+    'TUGRABAKH': '2422',
+    'UPPER BOWMAN': '2422',
+    'WALLANBAH': '2422',
+    'WARDS RIVER': '2422',
+    'WAUKIVORY': '2422',
+    'WOKO': '2422',
+    'BOMBAH POINT': '2423',
+    'BOOLAMBAYTE': '2423',
+    'BULAHDELAH': '2423',
+    'BUNGWAHL': '2423',
+    'COOLONGOLOOK': '2423',
+    'CRAWFORD RIVER': '2423',
+    'MARKWELL': '2423',
+    'MAYERS FLAT': '2423',
+    'MUNGO BRUSH': '2423',
+    'MYALL LAKE': '2423',
+    'NERONG': '2423',
+    'SEAL ROCKS': '2423',
+    'TOPI TOPI': '2423',
+    'UPPER MYALL': '2423',
+    'VIOLET HILL': '2423',
+    'WANG WAUK': '2423',
+    'WARRANULLA': '2423',
+    'WILLINA': '2423',
+    'WOOTTON': '2423',
+    'YAGON': '2423',
+    'CAFFREYS FLAT': '2424',
+    'CELLS RIVER': '2424',
+    'COOPLACURRIPA': '2424',
+    'CUNDLE FLAT': '2424',
+    'KNORRIT FLAT': '2424',
+    'KNORRIT FOREST': '2424',
+    'MOUNT GEORGE': '2424',
+    'NUMBER ONE': '2424',
+    'TIRI': '2424',
+    'ALLWORTH': '2425',
+    'BOORAL': '2425',
+    'GIRVAN': '2425',
+    'STROUD': '2425',
+    'THE BRANCH': '2425',
+    'WASHPOOL': '2425',
+    'COOPERNOOK': '2426',
+    'LANGLEY VALE': '2426',
+    'MOTO': '2426',
+    'CROWDY HEAD': '2427',
+    'HARRINGTON': '2427',
+    'BLUEYS BEACH': '2428',
+    'BOOMERANG BEACH': '2428',
+    'BOOTI BOOTI': '2428',
+    'CHARLOTTE BAY': '2428',
+    'COOMBA BAY': '2428',
+    'COOMBA PARK': '2428',
+    'DARAWANK': '2428',
+    'ELIZABETH BEACH': '2428',
+    'FORSTER': '2428',
+    'FORSTER SHOPPING VILLAGE': '2428',
+    'PACIFIC PALMS': '2428',
+    'SANDBAR': '2428',
+    'SHALLOW BAY': '2428',
+    'SMITHS LAKE': '2428',
+    'TARBUCK BAY': '2428',
+    'TIONA': '2428',
+    'TUNCURRY': '2428',
+    'WALLINGAT': '2428',
+    'WALLIS LAKE': '2428',
+    'WHOOTA': '2428',
+    'BOBIN': '2429',
+    'BOORGANNA': '2429',
+    'BUCCA WAUKA': '2429',
+    'BULGA FOREST': '2429',
+    'BUNYAH': '2429',
+    'BURRELL CREEK': '2429',
+    'CAPARRA': '2429',
+    'CEDAR PARTY': '2429',
+    'COMBOYNE': '2429',
+    'DINGO FOREST': '2429',
+    'DOLLYS FLAT': '2429',
+    'DYERS CROSSING': '2429',
+    'ELANDS': '2429',
+    'FIREFLY': '2429',
+    'INNES VIEW': '2429',
+    'KARAAK FLAT': '2429',
+    'KHATAMBUHL': '2429',
+    'KILLABAKH': '2429',
+    'KILLAWARRA': '2429',
+    'KIMBRIKI': '2429',
+    'KIPPAXS': '2429',
+    'KRAMBACH': '2429',
+    'KUNDIBAKH': '2429',
+    'MARLEE': '2429',
+    'MOORAL CREEK': '2429',
+    'STRATHCEDAR': '2429',
+    'THE BIGHT': '2429',
+    'TIPPERARY': '2429',
+    'WARRIWILLAH': '2429',
+    'WHERROL FLAT': '2429',
+    'WINGHAM': '2429',
+    'YARRATT FOREST': '2429',
+    'BLACK HEAD': '2430',
+    'BOHNOCK': '2430',
+    'BOOTAWA': '2430',
+    'BRIMBIN': '2430',
+    'CABBAGE TREE ISLAND': '2430',
+    'CHATHAM': '2430',
+    'CROKI': '2430',
+    'CUNDLETOWN': '2430',
+    'DIAMOND BEACH': '2430',
+    'DUMARESQ ISLAND': '2430',
+    'FAILFORD': '2430',
+    'GHINNI GHINNI': '2430',
+    'GLENTHORNE': '2430',
+    'HALLIDAYS POINT': '2430',
+    'HILLVILLE': '2430',
+    'JONES ISLAND': '2430',
+    'KIWARRAK': '2430',
+    'KOORAINGHAT': '2430',
+    'KUNDLE KUNDLE': '2430',
+    'LANSDOWNE FOREST': '2430',
+    'MANNING POINT': '2430',
+    'MELINGA': '2430',
+    'MITCHELLS ISLAND': '2430',
+    'MONDROOK': '2430',
+    'OLD BAR': '2430',
+    'OXLEY ISLAND': '2430',
+    'PAMPOOLAH': '2430',
+    'POSSUM BRUSH': '2430',
+    'PURFLEET': '2430',
+    'RAINBOW FLAT': '2430',
+    'RED HEAD': '2430',
+    'SALTWATER': '2430',
+    'TALLWOODS VILLAGE': '2430',
+    'TAREE': '2430',
+    'TAREE SOUTH': '2430',
+    'TINONEE': '2430',
+    'UPPER LANSDOWNE': '2430',
+    'WALLABI POINT': '2430',
+    'ARAKOON': '2431',
+    'JERSEYVILLE': '2431',
+    'SOUTH WEST ROCKS': '2431',
+    'BATAR CREEK': '2439',
+    'BLACK CREEK': '2439',
+    'KENDALL': '2439',
+    'KEREWONG': '2439',
+    'KEW': '2439',
+    'LOGANS CROSSING': '2439',
+    'LORNE': '2439',
+    'ROSSGLEN': '2439',
+    'SWANS CROSSING': '2439',
+    'UPSALLS CREEK': '2439',
+    'ALDAVILLA': '2440',
+    'AUSTRAL EDEN': '2440',
+    'BELLBROOK': '2440',
+    'BELLIMBOPINNI': '2440',
+    'BELMORE RIVER': '2440',
+    'BURNT BRIDGE': '2440',
+    'CARRAI': '2440',
+    'CLYBUCCA': '2440',
+    'COLLOMBATTI': '2440',
+    'COMARA': '2440',
+    'CORANGULA': '2440',
+    'CRESCENT HEAD': '2440',
+    'DEEP CREEK': '2440',
+    'DONDINGALONG': '2440',
+    'EAST KEMPSEY': '2440',
+    'EUROKA': '2440',
+    'FREDERICKTON': '2440',
+    'GLADSTONE': '2440',
+    'GREENHILL': '2440',
+    'HAMPDEN HALL': '2440',
+    'HAT HEAD': '2440',
+    'HICKEYS CREEK': '2440',
+    'KEMPSEY': '2440',
+    'KINCHELA': '2440',
+    'LOWER CREEK': '2440',
+    'MILLBANK': '2440',
+    'MOONEBA': '2440',
+    'MOPARRABAH': '2440',
+    'MUNGAY CREEK': '2440',
+    'OLD STATION': '2440',
+    'POLA CREEK': '2440',
+    'RAINBOW REACH': '2440',
+    'SEVEN OAKS': '2440',
+    'SHERWOOD': '2440',
+    'SKILLION FLAT': '2440',
+    'SMITHTOWN': '2440',
+    'SOUTH KEMPSEY': '2440',
+    'SUMMER ISLAND': '2440',
+    'TEMAGOG': '2440',
+    'TOOROOKA': '2440',
+    'TURNERS FLAT': '2440',
+    'VERGES CREEK': '2440',
+    'WEST KEMPSEY': '2440',
+    'WILLAWARRIN': '2440',
+    'WILLI WILLI': '2440',
+    'WITTITRIN': '2440',
+    'YARRAVEL': '2440',
+    'YESSABAH': '2440',
+    'ALLGOMERA': '2441',
+    'BALLENGARRA': '2441',
+    'BARRAGANYATTI': '2441',
+    'BONVILLE': '2441',
+    'BRIL BRIL': '2441',
+    'BRINERVILLE': '2441',
+    'COOPERABUNG': '2441',
+    'EUNGAI CREEK': '2441',
+    'EUNGAI RAIL': '2441',
+    'FISHERMANS REACH': '2441',
+    'GRASSY HEAD': '2441',
+    'GUM SCRUB': '2441',
+    'HACKS FERRY': '2441',
+    'KIPPARA': '2441',
+    'KUNDABUNG': '2441',
+    'ROLLANDS PLAINS': '2441',
+    'STUARTS POINT': '2441',
+    'TAMBAN': '2441',
+    'TELEGRAPH POINT': '2441',
+    'UPPER ROLLANDS PLAINS': '2441',
+    'YARRAHAPINNI': '2441',
+    'BOBS CREEK': '2443',
+    'CAMDEN HEAD': '2443',
+    'CORALVILLE': '2443',
+    'DEAUVILLE': '2443',
+    'DIAMOND HEAD': '2443',
+    'DUNBOGAN': '2443',
+    'HANNAM VALE': '2443',
+    'HERONS CREEK': '2443',
+    'JOHNS RIVER': '2443',
+    'LAKEWOOD': '2443',
+    'LAURIETON': '2443',
+    'MIDDLE BROTHER': '2443',
+    'MOORLAND': '2443',
+    'NORTH BROTHER': '2443',
+    'NORTH HAVEN': '2443',
+    'STEWARTS RIVER': '2443',
+    'WAITUI': '2443',
+    'WEST HAVEN': '2443',
+    'BLACKMANS POINT': '2444',
+    'FERNBANK CREEK': '2444',
+    'FLYNNS BEACH': '2444',
+    'LIGHTHOUSE BEACH': '2444',
+    'NORTH SHORE': '2444',
+    'PORT MACQUARIE': '2444',
+    'PORT MACQUARIE BC': '2444',
+    'RIVERSIDE': '2444',
+    'SETTLEMENT CITY': '2444',
+    'THE HATCH': '2444',
+    'THRUMSTER': '2444',
+    'BONNY HILLS': '2445',
+    'GRANTS BEACH': '2445',
+    'JOLLY NOSE': '2445',
+    'LAKE CATHIE': '2445',
+    'BAGNOO': '2446',
+    'BAGO': '2446',
+    'BANDA BANDA': '2446',
+    'BEECHWOOD': '2446',
+    'BELLANGRY': '2446',
+    'BIRDWOOD': '2446',
+    'BROMBIN': '2446',
+    'BYABARRA': '2446',
+    'CAIRNCROSS': '2446',
+    'CROSSLANDS': '2446',
+    'DEBENHAM': '2446',
+    'DOYLES RIVER': '2446',
+    'ELLENBOROUGH': '2446',
+    'FORBES RIVER': '2446',
+    'FRAZERS CREEK': '2446',
+    'GEARYS FLAT': '2446',
+    'HARTYS PLAINS': '2446',
+    'HOLLISDALE': '2446',
+    'HUNTINGDON': '2446',
+    'HYNDMANS CREEK': '2446',
+    'KINDEE': '2446',
+    'KING CREEK': '2446',
+    'LAKE INNES': '2446',
+    'LONG FLAT': '2446',
+    'LOWER PAPPINBARRA': '2446',
+    'MARLO MERRICAN': '2446',
+    'MORTONS CREEK': '2446',
+    'MOUNT SEAVIEW': '2446',
+    'PAPPINBARRA': '2446',
+    'PEMBROOKE': '2446',
+    'PIPECLAY': '2446',
+    'RAWDON ISLAND': '2446',
+    'REDBANK': '2446',
+    'ROSEWOOD': '2446',
+    'SANCROX': '2446',
+    'TOMS CREEK': '2446',
+    'UPPER PAPPINBARRA': '2446',
+    'WAUCHOPE': '2446',
+    'WERRIKIMBE': '2446',
+    'YARRAS': '2446',
+    'YIPPIN CREEK': '2446',
+    'BURRAPINE': '2447',
+    'CONGARINNI': '2447',
+    'CONGARINNI NORTH': '2447',
+    'DONNELLYVILLE': '2447',
+    'GUMMA': '2447',
+    'MACKSVILLE': '2447',
+    'NEWEE CREEK': '2447',
+    'NORTH MACKSVILLE': '2447',
+    'SCOTTS HEAD': '2447',
+    'TALARM': '2447',
+    'TAYLORS ARM': '2447',
+    'THUMB CREEK': '2447',
+    'UPPER TAYLORS ARM': '2447',
+    'UTUNGUN': '2447',
+    'WARRELL CREEK': '2447',
+    'WAY WAY': '2447',
+    'WIRRIMBI': '2447',
+    'YARRANBELLA': '2447',
+    'HYLAND PARK': '2448',
+    'NAMBUCCA HEADS': '2448',
+    'VALLA': '2448',
+    'VALLA BEACH': '2448',
+    'ARGENTS HILL': '2449',
+    'BOWRAVILLE': '2449',
+    'BUCKRA BENDINNI': '2449',
+    'GIRRALONG': '2449',
+    'KENNAICLE CREEK': '2449',
+    'KILLIEKRANKIE': '2449',
+    'MISSABOTTI': '2449',
+    'SOUTH ARM': '2449',
+    'TEWINGA': '2449',
+    'BOAMBEE': '2450',
+    'BROOKLANA': '2450',
+    'BUCCA': '2450',
+    'COFFS HARBOUR': '2450',
+    'COFFS HARBOUR JETTY': '2450',
+    'COFFS HARBOUR PLAZA': '2450',
+    'CORAMBA': '2450',
+    'GLENREAGH': '2450',
+    'KARANGI': '2450',
+    'KORORA': '2450',
+    'LOWANNA': '2450',
+    'MOONEE BEACH': '2450',
+    'NANA GLEN': '2450',
+    'NORTH BOAMBEE VALLEY': '2450',
+    'SAPPHIRE BEACH': '2450',
+    'ULONG': '2450',
+    'UPPER ORARA': '2450',
+    'BOAMBEE EAST': '2452',
+    'SAWTELL': '2452',
+    'TOORMINA': '2452',
+    'BIELSDOWN HILLS': '2453',
+    'BILLYS CREEK': '2453',
+    'BOSTOBRICK': '2453',
+    'CASCADE': '2453',
+    'CLOUDS CREEK': '2453',
+    'DEER VALE': '2453',
+    'DORRIGO': '2453',
+    'DORRIGO MOUNTAIN': '2453',
+    'DUNDURRABIN': '2453',
+    'EBOR': '2453',
+    'FERNBROOK': '2453',
+    'GANGARA': '2453',
+    'HARNESS CASK': '2453',
+    'HERNANI': '2453',
+    'MARENGO': '2453',
+    'MEGAN': '2453',
+    'MELDRUM DOWNS': '2453',
+    'MOONPAR': '2453',
+    'NEVER NEVER': '2453',
+    'NORTH DORRIGO': '2453',
+    'PADDYS PLAIN': '2453',
+    'TALLOWWOOD RIDGE': '2453',
+    'TYRINGHAM': '2453',
+    'WILD CATTLE CREEK': '2453',
+    'BELLINGEN': '2454',
+    'BRIERFIELD': '2454',
+    'BUFFER CREEK': '2454',
+    'BUNDAGEN': '2454',
+    'DARKWOOD': '2454',
+    'FERNMOUNT': '2454',
+    'GLENIFFER': '2454',
+    'KALANG': '2454',
+    'KOOROOWI': '2454',
+    'MARX HILL': '2454',
+    'MYLESTOM': '2454',
+    'NORTH BELLINGEN': '2454',
+    'PROMISED LAND': '2454',
+    'RALEIGH': '2454',
+    'REPTON': '2454',
+    'SCOTCHMAN': '2454',
+    'SPICKETTS CREEK': '2454',
+    'THORA': '2454',
+    'VALERY': '2454',
+    'NEWRY': '2455',
+    'NEWRY ISLAND': '2455',
+    'URUNGA': '2455',
+    'WENONAH HEAD': '2455',
+    'ARRAWARRA': '2456',
+    'ARRAWARRA HEADLAND': '2456',
+    'CORINDI BEACH': '2456',
+    'EMERALD BEACH': '2456',
+    'MULLAWAY': '2456',
+    'RED ROCK': '2456',
+    'SAFETY BEACH': '2456',
+    'SANDY BEACH': '2456',
+    'UPPER CORINDI': '2456',
+    'WOOLGOOLGA': '2456',
+    'ALICE': '2460',
+    'ALUMY CREEK': '2460',
+    'BARCOONGERE': '2460',
+    'BARRETTS CREEK': '2460',
+    'BARYULGIL': '2460',
+    'BLAXLANDS CREEK': '2460',
+    'BOM BOM': '2460',
+    'BOOKRAM': '2460',
+    'BRAUNSTONE': '2460',
+    'BRUSHGROVE': '2460',
+    'BUCCARUMBI': '2460',
+    'CALAMIA': '2460',
+    'CANGAI': '2460',
+    'CARNHAM': '2460',
+    'CARRS CREEK': '2460',
+    'CARRS ISLAND': '2460',
+    'CARRS PENINSULAR': '2460',
+    'CHAELUNDI': '2460',
+    'CHAMBIGNE': '2460',
+    'CLARENZA': '2460',
+    'CLIFDEN': '2460',
+    'COALDALE': '2460',
+    'COLLUM COLLUM': '2460',
+    'COOMBADJHA': '2460',
+    'COPMANHURST': '2460',
+    'COUTTS CROSSING': '2460',
+    'COWPER': '2460',
+    'CROWTHER ISLAND': '2460',
+    'DALMORTON': '2460',
+    'DILKOON': '2460',
+    'DIRTY CREEK': '2460',
+    'DUMBUDGERY': '2460',
+    'EATONSVILLE': '2460',
+    'EIGHTEEN MILE': '2460',
+    'ELLAND': '2460',
+    'FINE FLOWER': '2460',
+    'FORTIS CREEK': '2460',
+    'GLENUGIE': '2460',
+    'GRAFTON': '2460',
+    'GRAFTON WEST': '2460',
+    'GREAT MARLOW': '2460',
+    'GURRANANG': '2460',
+    'HALFWAY CREEK': '2460',
+    'HEIFER STATION': '2460',
+    'JACKADGERY': '2460',
+    'JUNCTION HILL': '2460',
+    'KANGAROO CREEK': '2460',
+    'KEYBARBIN': '2460',
+    'KOOLKHAN': '2460',
+    'KREMNOS': '2460',
+    'KUNGALA': '2460',
+    'KYARRAN': '2460',
+    'LANITZA': '2460',
+    'LAWRENCE': '2460',
+    'LEVENSTRATH': '2460',
+    'LILYDALE': '2460',
+    'LIONSVILLE': '2460',
+    'LOWER SOUTHGATE': '2460',
+    'MALABUGILMAH': '2460',
+    'MOLEVILLE CREEK': '2460',
+    'MOUNTAIN VIEW': '2460',
+    'MYLNEFORD': '2460',
+    'NEWBOLD': '2460',
+    'NYMBOIDA': '2460',
+    'PULGANBAR': '2460',
+    'RAMORNIE': '2460',
+    'RUSHFORTH': '2460',
+    'SANDY CROSSING': '2460',
+    'SEELANDS': '2460',
+    'SHANNONDALE': '2460',
+    'SMITHS CREEK': '2460',
+    'SOUTH GRAFTON': '2460',
+    'SOUTHAMPTON': '2460',
+    'SOUTHGATE': '2460',
+    'STOCKYARD CREEK': '2460',
+    'THE PINNACLES': '2460',
+    'THE WHITEMAN': '2460',
+    'TOWALLUM': '2460',
+    'TRENAYR': '2460',
+    'TYNDALE': '2460',
+    'UPPER COPMANHURST': '2460',
+    'UPPER FINE FLOWER': '2460',
+    'WARRAGAI CREEK': '2460',
+    'WATERVIEW': '2460',
+    'WATERVIEW HEIGHTS': '2460',
+    'WELLS CROSSING': '2460',
+    'WHITEMAN CREEK': '2460',
+    'WINEGROVE': '2460',
+    'WOMBAT CREEK': '2460',
+    'CALLIOPE': '2462',
+    'COLDSTREAM': '2462',
+    'DIGGERS CAMP': '2462',
+    'GILLETTS RIDGE': '2462',
+    'LAKE HIAWATHA': '2462',
+    'LAVADIA': '2462',
+    'MINNIE WATER': '2462',
+    'PILLAR VALLEY': '2462',
+    'SWAN CREEK': '2462',
+    'TUCABIA': '2462',
+    'ULMARRA': '2462',
+    'WOOLI': '2462',
+    'ASHBY': '2463',
+    'ASHBY HEIGHTS': '2463',
+    'ASHBY ISLAND': '2463',
+    'BROOMS HEAD': '2463',
+    'GULMARRAD': '2463',
+    'ILARWILL': '2463',
+    'JACKY BULBIN FLAT': '2463',
+    'JAMES CREEK': '2463',
+    'MACLEAN': '2463',
+    'PALMERS CHANNEL': '2463',
+    'PALMERS ISLAND': '2463',
+    'SANDON': '2463',
+    'SHARK CREEK': '2463',
+    'TALOUMBI': '2463',
+    'THE SANDON': '2463',
+    'TOWNSEND': '2463',
+    'TULLYMORGAN': '2463',
+    'WOODFORD ISLAND': '2463',
+    'ANGOURIE': '2464',
+    'FREEBURN ISLAND': '2464',
+    'MICALO ISLAND': '2464',
+    'WOOLOWEYAH': '2464',
+    'YAMBA': '2464',
+    'YURAYGIR': '2464',
+    'HARWOOD': '2465',
+    'ILUKA': '2466',
+    'THE FRESHWATER': '2466',
+    'WOODY HEAD': '2466',
+    'BANYABBA': '2469',
+    'BEAN CREEK': '2469',
+    'BINGEEBEEBRA CREEK': '2469',
+    'BONALBO': '2469',
+    'BOOMOODEERIE': '2469',
+    'BOTTLE CREEK': '2469',
+    'BULLDOG': '2469',
+    'BUNGAWALBIN': '2469',
+    'BUSBYS FLAT': '2469',
+    'CAMBRIDGE PLATEAU': '2469',
+    'CAMIRA': '2469',
+    'CAPEEN CREEK': '2469',
+    'CHATSWORTH': '2469',
+    'CLEARFIELD': '2469',
+    'COONGBAR': '2469',
+    'CULMARAN CREEK': '2469',
+    'DRAKE': '2469',
+    'DRAKE VILLAGE': '2469',
+    'DUCK CREEK': '2469',
+    'EWINGAR': '2469',
+    'GIBBERAGEE': '2469',
+    'GOODWOOD ISLAND': '2469',
+    'GORGE CREEK': '2469',
+    'HAYSTACK': '2469',
+    'HOGARTH RANGE': '2469',
+    'JACKSONS FLAT': '2469',
+    'JOES BOX': '2469',
+    'KIPPENDUFF': '2469',
+    'LOUISA CREEK': '2469',
+    'LOWER BOTTLE CREEK': '2469',
+    'LOWER DUCK CREEK': '2469',
+    'LOWER PEACOCK': '2469',
+    'MALLANGANEE': '2469',
+    'MOOKIMA WYBRA': '2469',
+    'MORORO': '2469',
+    'MOUNT MARSH': '2469',
+    'MUMMULGUM': '2469',
+    'MYRTLE CREEK': '2469',
+    'OLD BONALBO': '2469',
+    'PADDYS FLAT': '2469',
+    'PAGANS FLAT': '2469',
+    'PEACOCK CREEK': '2469',
+    'PIKAPENE': '2469',
+    'PRETTY GULLY': '2469',
+    'RAPPVILLE': '2469',
+    'SANDILANDS': '2469',
+    'SIMPKINS CREEK': '2469',
+    'SIX MILE SWAMP': '2469',
+    'TABULAM': '2469',
+    'THERESA CREEK': '2469',
+    'TUNGLEBUNG': '2469',
+    'UPPER DUCK CREEK': '2469',
+    'WARREGAH ISLAND': '2469',
+    'WHIPORIE': '2469',
+    'WOOMBAH': '2469',
+    'WYAN': '2469',
+    'YABBRA': '2469',
+    'BABYL CREEK': '2470',
+    'BACKMEDE': '2470',
+    'BARAIMAL': '2470',
+    'CASINO': '2470',
+    'COOLANESS': '2470',
+    'COOMBELL': '2470',
+    'DOBIES BIGHT': '2470',
+    'DOUBTFUL CREEK': '2470',
+    'DYRAABA': '2470',
+    'ELLANGOWAN': '2470',
+    'FAIRY HILL': '2470',
+    'IRVINGTON': '2470',
+    'LEEVILLE': '2470',
+    'LOWER DYRAABA': '2470',
+    'MONGOGARIE': '2470',
+    'NAUGHTONS GAP': '2470',
+    'NORTH CASINO': '2470',
+    'PIORA': '2470',
+    'SEXTONVILLE': '2470',
+    'SHANNON BROOK': '2470',
+    'SPRING GROVE': '2470',
+    'STRATHEDEN': '2470',
+    'UPPER MONGOGARIE': '2470',
+    'WOODVIEW': '2470',
+    'WOOLNERS ARM': '2470',
+    'WOOROOWOOLGAN': '2470',
+    'YORKLEA': '2470',
+    'BORA RIDGE': '2471',
+    'CODRINGTON': '2471',
+    'CORAKI': '2471',
+    'EAST CORAKI': '2471',
+    'GREEN FOREST': '2471',
+    'GREENRIDGE': '2471',
+    'NORTH WOODBURN': '2471',
+    'TATHAM': '2471',
+    'WEST CORAKI': '2471',
+    'BROADWATER': '2472',
+    'BUCKENDOON': '2472',
+    'ESK': '2472',
+    'KILGIN': '2472',
+    'MOONEM': '2472',
+    'NEW ITALY': '2472',
+    'RILEYS HILL': '2472',
+    'TABBIMOBLE': '2472',
+    'TRUSTUMS HILL': '2472',
+    'WOODBURN': '2472',
+    'BUNDJALUNG': '2473',
+    'DOONBAH': '2473',
+    'EVANS HEAD': '2473',
+    'IRON GATES': '2473',
+    'SOUTH EVANS HEAD': '2473',
+    'AFTERLEE': '2474',
+    'BARKERS VALE': '2474',
+    'BORDER RANGES': '2474',
+    'CAWONGLA': '2474',
+    'CEDAR POINT': '2474',
+    'COLLINS CREEK': '2474',
+    'COUGAL': '2474',
+    'DAIRY FLAT': '2474',
+    'EDEN CREEK': '2474',
+    'EDENVILLE': '2474',
+    'ETTRICK': '2474',
+    'FAWCETTS PLAIN': '2474',
+    'FINDON CREEK': '2474',
+    'GENEVA': '2474',
+    'GHINNI GHI': '2474',
+    'GRADYS CREEK': '2474',
+    'GREEN PIGEON': '2474',
+    'GREVILLIA': '2474',
+    'HOMELEIGH': '2474',
+    'HORSE STATION CREEK': '2474',
+    'HORSESHOE CREEK': '2474',
+    'IRON POT CREEK': '2474',
+    'KILGRA': '2474',
+    'KYOGLE': '2474',
+    'LITTLE BACK CREEK': '2474',
+    'LOADSTONE': '2474',
+    'LYNCHS CREEK': '2474',
+    'NEW PARK': '2474',
+    'OLD GREVILLIA': '2474',
+    'ROSEBERRY': '2474',
+    'ROSEBERRY CREEK': '2474',
+    'RUKENVALE': '2474',
+    'SAWPIT CREEK': '2474',
+    'TERRACE CREEK': '2474',
+    'THE RISK': '2474',
+    'TOONUMBAR': '2474',
+    'UNUMGAR': '2474',
+    'UPPER EDEN CREEK': '2474',
+    'UPPER HORSESHOE CREEK': '2474',
+    'WADEVILLE': '2474',
+    'WARRAZAMBIL CREEK': '2474',
+    'WEST WIANGAREE': '2474',
+    'WIANGAREE': '2474',
+    'WYNEDEN': '2474',
+    'TOOLOOM': '2475',
+    'UPPER TOOLOOM': '2475',
+    'URBENVILLE': '2475',
+    'WALLABY CREEK': '2475',
+    'ACACIA CREEK': '2476',
+    'ACACIA PLATEAU': '2476',
+    'BOOMI CREEK': '2476',
+    'BRUMBY PLAINS': '2476',
+    'KOREELAH': '2476',
+    'LEGUME': '2476',
+    'LINDESAY CREEK': '2476',
+    'LOWER ACACIA CREEK': '2476',
+    'MULI MULI': '2476',
+    'OLD KOREELAH': '2476',
+    'THE GLEN': '2476',
+    'WOODENBONG': '2476',
+    'ALSTONVALE': '2477',
+    'ALSTONVILLE': '2477',
+    'BAGOTVILLE': '2477',
+    'EAST WARDELL': '2477',
+    'GOAT ISLAND': '2477',
+    'LYNWOOD': '2477',
+    'MEERSCHAUM VALE': '2477',
+    'PEARCES CREEK': '2477',
+    'ROUS': '2477',
+    'ROUS MILL': '2477',
+    'TUCKOMBIL': '2477',
+    'URALBA': '2477',
+    'WARDELL': '2477',
+    'WOLLONGBAR': '2477',
+    'BALLINA': '2478',
+    'COOLGARDIE': '2478',
+    'CUMBALUM': '2478',
+    'EAST BALLINA': '2478',
+    'EMPIRE VALE': '2478',
+    'KEITH HALL': '2478',
+    'LENNOX HEAD': '2478',
+    'PATCHS BEACH': '2478',
+    'PIMLICO': '2478',
+    'PIMLICO ISLAND': '2478',
+    'SKENNARS HEAD': '2478',
+    'SOUTH BALLINA': '2478',
+    'TEVEN': '2478',
+    'TINTENBAR': '2478',
+    'WEST BALLINA': '2478',
+    'BANGALOW': '2479',
+    'BINNA BURRA': '2479',
+    'BROOKLET': '2479',
+    'COOPERS SHOOT': '2479',
+    'COORABELL': '2479',
+    'FERNLEIGH': '2479',
+    'KNOCKROW': '2479',
+    'MCLEODS SHOOT': '2479',
+    'NASHUA': '2479',
+    'NEWRYBAR': '2479',
+    'OPOSSUM CREEK': '2479',
+    'POSSUM CREEK': '2479',
+    'BENTLEY': '2480',
+    'BEXHILL': '2480',
+    'BLAKEBROOK': '2480',
+    'BLUE KNOB': '2480',
+    'BOOERIE CREEK': '2480',
+    'BOORABEE PARK': '2480',
+    'BOOYONG': '2480',
+    'BUNGABBEE': '2480',
+    'CANIABA': '2480',
+    'CHILCOTTS GRASS': '2480',
+    'CLOVASS': '2480',
+    'CLUNES': '2480',
+    'COFFEE CAMP': '2480',
+    'CORNDALE': '2480',
+    'DORROUGHBY': '2480',
+    'DUNGARUBBA': '2480',
+    'DUNOON': '2480',
+    'EAST LISMORE': '2480',
+    'ELTHAM': '2480',
+    'EUREKA': '2480',
+    'FEDERAL': '2480',
+    'FERNSIDE': '2480',
+    'GEORGICA': '2480',
+    'GIRARDS HILL': '2480',
+    'GOOLMANGAR': '2480',
+    'GOONELLABAH': '2480',
+    'HOWARDS GRASS': '2480',
+    'JIGGI': '2480',
+    'KEERRONG': '2480',
+    'KOONORIGAN': '2480',
+    'LAGOON GRASS': '2480',
+    'LARNOOK': '2480',
+    'LEYCESTER': '2480',
+    'LILLIAN ROCK': '2480',
+    'LINDENDALE': '2480',
+    'LISMORE': '2480',
+    'LISMORE HEIGHTS': '2480',
+    'LOFTVILLE': '2480',
+    'MAROM CREEK': '2480',
+    'MCKEES HILL': '2480',
+    'MCLEANS RIDGES': '2480',
+    'MODANVILLE': '2480',
+    'MONALTRIE': '2480',
+    'MOUNTAIN TOP': '2480',
+    'NIGHTCAP': '2480',
+    'NIMBIN': '2480',
+    'NORTH LISMORE': '2480',
+    'NUMULGI': '2480',
+    'REPENTANCE CREEK': '2480',
+    'RICHMOND HILL': '2480',
+    'ROCK VALLEY': '2480',
+    'ROSEBANK': '2480',
+    'RUTHVEN': '2480',
+    'SOUTH GUNDURIMBA': '2480',
+    'SOUTH LISMORE': '2480',
+    'STONY CHUTE': '2480',
+    'TERANIA CREEK': '2480',
+    'THE CHANNON': '2480',
+    'TREGEAGLE': '2480',
+    'TUCKI TUCKI': '2480',
+    'TUCKURIMBA': '2480',
+    'TULLERA': '2480',
+    'TUNCESTER': '2480',
+    'TUNTABLE CREEK': '2480',
+    'WHIAN WHIAN': '2480',
+    'WOODLAWN': '2480',
+    'WYRALLAH': '2480',
+    'BROKEN HEAD': '2481',
+    'BYRON BAY': '2481',
+    'EWINGSDALE': '2481',
+    'HAYTERS HILL': '2481',
+    'MYOCUM': '2481',
+    'SKINNERS SHOOT': '2481',
+    'SUFFOLK PARK': '2481',
+    'TALOFA': '2481',
+    'TYAGARAH': '2481',
+    'GOONENGERRY': '2482',
+    'HUONBROOK': '2482',
+    'KOONYUM RANGE': '2482',
+    'MAIN ARM': '2482',
+    'MONTECOLLUM': '2482',
+    'MULLUMBIMBY': '2482',
+    'MULLUMBIMBY CREEK': '2482',
+    'PALMWOODS': '2482',
+    'UPPER COOPERS CREEK': '2482',
+    'UPPER MAIN ARM': '2482',
+    'UPPER WILSONS CREEK': '2482',
+    'WANGANUI': '2482',
+    'WILSONS CREEK': '2482',
+    'BILLINUDGEL': '2483',
+    'BRUNSWICK HEADS': '2483',
+    'BURRINGBAR': '2483',
+    'CRABBES CREEK': '2483',
+    'MIDDLE POCKET': '2483',
+    'MOOBALL': '2483',
+    'NEW BRIGHTON': '2483',
+    'OCEAN SHORES': '2483',
+    'SLEEPY HOLLOW': '2483',
+    'SOUTH GOLDEN BEACH': '2483',
+    'THE POCKET': '2483',
+    'UPPER BURRINGBAR': '2483',
+    'WOOYUNG': '2483',
+    'YELGUN': '2483',
+    'BRAY PARK': '2484',
+    'BRAYS CREEK': '2484',
+    'BYANGUM': '2484',
+    'BYRRILL CREEK': '2484',
+    'CHILLINGHAM': '2484',
+    'CHOWAN CREEK': '2484',
+    'CLOTHIERS CREEK': '2484',
+    'COMMISSIONERS CREEK': '2484',
+    'CONDONG': '2484',
+    'CRYSTAL CREEK': '2484',
+    'CUDGERA CREEK': '2484',
+    'DOON DOON': '2484',
+    'DULGUIGAN': '2484',
+    'DUM DUM': '2484',
+    'DUNBIBLE': '2484',
+    'DUNGAY': '2484',
+    'EUNGELLA': '2484',
+    'EVIRON': '2484',
+    'FARRANTS HILL': '2484',
+    'FERNVALE': '2484',
+    'HOPKINS CREEK': '2484',
+    'KIELVALE': '2484',
+    'KUNGHUR': '2484',
+    'KUNGHUR CREEK': '2484',
+    'KYNNUMBOON': '2484',
+    'LIMPINWOOD': '2484',
+    'MEBBIN': '2484',
+    'MIDGINBIL': '2484',
+    'MOUNT BURRELL': '2484',
+    'MOUNT WARNING': '2484',
+    'MURWILLUMBAH': '2484',
+    'MURWILLUMBAH SOUTH': '2484',
+    'NOBBYS CREEK': '2484',
+    'NORTH ARM': '2484',
+    'NUMINBAH': '2484',
+    'NUNDERI': '2484',
+    'PALMVALE': '2484',
+    'PUMPENBIL': '2484',
+    'RESERVE CREEK': '2484',
+    'ROUND MOUNTAIN': '2484',
+    'ROWLANDS CREEK': '2484',
+    'SOUTH MURWILLUMBAH': '2484',
+    'STOKERS SIDING': '2484',
+    'TERRAGON': '2484',
+    'TOMEWIN': '2484',
+    'TYALGUM': '2484',
+    'TYALGUM CREEK': '2484',
+    'TYGALGAH': '2484',
+    'UKI': '2484',
+    'UPPER CRYSTAL CREEK': '2484',
+    'URLIUP': '2484',
+    'WARDROP VALLEY': '2484',
+    'ZARA': '2484',
+    'TWEED HEADS': '2485',
+    'TWEED HEADS WEST': '2485',
+    'BANORA POINT': '2486',
+    'BILAMBIL': '2486',
+    'BILAMBIL HEIGHTS': '2486',
+    'BUNGALORA': '2486',
+    'CAROOL': '2486',
+    'COBAKI': '2486',
+    'COBAKI LAKES': '2486',
+    'DUROBY': '2486',
+    'GLENGARRIE': '2486',
+    'PIGGABEEN': '2486',
+    'TERRANORA': '2486',
+    'TWEED HEADS SOUTH': '2486',
+    'UPPER DUROBY': '2486',
+    'CHINDERAH': '2487',
+    'CUDGEN': '2487',
+    'DURANBAH': '2487',
+    'FINGAL HEAD': '2487',
+    'KINGS FOREST': '2487',
+    'KINGSCLIFF': '2487',
+    'STOTTS CREEK': '2487',
+    'BOGANGAR': '2488',
+    'CABARITA BEACH': '2488',
+    'TANGLEWOOD': '2488',
+    'HASTINGS POINT': '2489',
+    'POTTSVILLE': '2489',
+    'NORTH TUMBULGUM': '2490',
+    'TUMBULGUM': '2490',
+    'CONISTON': '2500',
+    'GWYNNEVILLE': '2500',
+    'KEIRAVILLE': '2500',
+    'MANGERTON': '2500',
+    'MOUNT KEIRA': '2500',
+    'MOUNT SAINT THOMAS': '2500',
+    'NORTH WOLLONGONG': '2500',
+    'SPRING HILL': '2500',
+    'WEST WOLLONGONG': '2500',
+    'WOLLONGONG': '2500',
+    'WOLLONGONG DC': '2500',
+    'WOLLONGONG WEST': '2500',
+    'CRINGILA': '2502',
+    'LAKE HEIGHTS': '2502',
+    'PRIMBEE': '2502',
+    'WARRAWONG': '2502',
+    'KEMBLAWARRA': '2505',
+    'PORT KEMBLA': '2505',
+    'BERKELEY': '2506',
+    'COALCLIFF': '2508',
+    'DARKES FOREST': '2508',
+    'HELENSBURGH': '2508',
+    'LILYVALE': '2508',
+    'MADDENS PLAINS': '2508',
+    'OTFORD': '2508',
+    'STANWELL PARK': '2508',
+    'STANWELL TOPS': '2508',
+    'WORONORA DAM': '2508',
+    'AUSTINMER': '2515',
+    'CLIFTON': '2515',
+    'COLEDALE': '2515',
+    'SCARBOROUGH': '2515',
+    'THIRROUL': '2515',
+    'WOMBARRA': '2515',
+    'BULLI': '2516',
+    'RUSSELL VALE': '2517',
+    'WOONONA': '2517',
+    'WOONONA EAST': '2517',
+    'BELLAMBI': '2518',
+    'CORRIMAL': '2518',
+    'CORRIMAL EAST': '2518',
+    'EAST CORRIMAL': '2518',
+    'TARRAWANNA': '2518',
+    'TOWRADGI': '2518',
+    'BALGOWNIE': '2519',
+    'FAIRY MEADOW': '2519',
+    'FERNHILL': '2519',
+    'MOUNT OUSLEY': '2519',
+    'MOUNT PLEASANT': '2519',
+    'REIDTOWN': '2519',
+    'FIGTREE': '2525',
+    'CORDEAUX': '2526',
+    'CORDEAUX HEIGHTS': '2526',
+    'DOMBARTON': '2526',
+    'FARMBOROUGH HEIGHTS': '2526',
+    'KEMBLA GRANGE': '2526',
+    'KEMBLA HEIGHTS': '2526',
+    'MOUNT KEMBLA': '2526',
+    'UNANDERRA': '2526',
+    'UNANDERRA DC': '2526',
+    'ALBION PARK': '2527',
+    'ALBION PARK RAIL': '2527',
+    'CALDERWOOD': '2527',
+    'CROOM': '2527',
+    'NORTH MACQUARIE': '2527',
+    'TONGARRA': '2527',
+    'TULLIMBAR': '2527',
+    'YELLOW ROCK': '2527',
+    'BARRACK HEIGHTS': '2528',
+    'BARRACK POINT': '2528',
+    'LAKE ILLAWARRA': '2528',
+    'MOUNT WARRIGAL': '2528',
+    'WARILLA': '2528',
+    'WINDANG': '2528',
+    'BLACKBUTT': '2529',
+    'DUNMORE': '2529',
+    'FLINDERS': '2529',
+    'OAK FLATS': '2529',
+    'OAK FLATS DC': '2529',
+    'SHELL COVE': '2529',
+    'SHELLHARBOUR': '2529',
+    'SHELLHARBOUR CITY CENTRE': '2529',
+    'AVONDALE': '2530',
+    'BROWNSVILLE': '2530',
+    'CLEVELAND': '2530',
+    'DAPTO': '2530',
+    'HAYWARDS BAY': '2530',
+    'HORSLEY': '2530',
+    'HUNTLEY': '2530',
+    'KANAHOOKA': '2530',
+    'KOONAWARRA': '2530',
+    'MARSHALL MOUNT': '2530',
+    'PENROSE': '2530',
+    'WONGAWILLI': '2530',
+    'YALLAH': '2530',
+    'BOMBO': '2533',
+    'CURRAMORE': '2533',
+    'JAMBEROO': '2533',
+    'JERRARA': '2533',
+    'KIAMA': '2533',
+    'KIAMA DOWNS': '2533',
+    'KIAMA HEIGHTS': '2533',
+    'MINNAMURRA': '2533',
+    'SADDLEBACK MOUNTAIN': '2533',
+    'FOXGROUND': '2534',
+    'GERRINGONG': '2534',
+    'GERROA': '2534',
+    'ROSE VALLEY': '2534',
+    'TOOLIJOOA': '2534',
+    'WERRI BEACH': '2534',
+    'WILLOW VALE': '2534',
+    'BACK FOREST': '2535',
+    'BELLAWONGARAH': '2535',
+    'BERRY': '2535',
+    'BERRY MOUNTAIN': '2535',
+    'BROGERS CREEK': '2535',
+    'BROUGHTON': '2535',
+    'BROUGHTON VALE': '2535',
+    'BROUGHTON VILLAGE': '2535',
+    'BUDDEROO': '2535',
+    'BUNDEWALLAH': '2535',
+    'COOLANGATTA': '2535',
+    'FAR MEADOW': '2535',
+    'JASPERS BRUSH': '2535',
+    'SHOALHAVEN HEADS': '2535',
+    'WATTAMOLLA': '2535',
+    'WOODHILL': '2535',
+    'BATEHAVEN': '2536',
+    'BATEMANS BAY': '2536',
+    'BENANDARAH': '2536',
+    'BIMBIMBIE': '2536',
+    'BUCKENBOWRA': '2536',
+    'CATALINA': '2536',
+    'CURROWAN': '2536',
+    'DENHAMS BEACH': '2536',
+    'DEPOT BEACH': '2536',
+    'DURRAS NORTH': '2536',
+    'EAST LYNNE': '2536',
+    'GUERILLA BAY': '2536',
+    'JEREMADRA': '2536',
+    'LONG BEACH': '2536',
+    'MALONEYS BEACH': '2536',
+    'MALUA BAY': '2536',
+    'MOGO': '2536',
+    'NELLIGEN': '2536',
+    'NORTH BATEMANS BAY': '2536',
+    'PEBBLY BEACH': '2536',
+    'ROSEDALE': '2536',
+    'RUNNYFORD': '2536',
+    'SOUTH DURRAS': '2536',
+    'SUNSHINE BAY': '2536',
+    'SURF BEACH': '2536',
+    'SURFSIDE': '2536',
+    'WOODLANDS': '2536',
+    'BERGALIA': '2537',
+    'BINGIE': '2537',
+    'BROULEE': '2537',
+    'COILA': '2537',
+    'CONGO': '2537',
+    'DEUA': '2537',
+    'DEUA RIVER VALLEY': '2537',
+    'KIORA': '2537',
+    'MERINGO': '2537',
+    'MOGENDOURA': '2537',
+    'MORUYA': '2537',
+    'MORUYA HEADS': '2537',
+    'MOSSY POINT': '2537',
+    'TOMAKIN': '2537',
+    'TURLINJAH': '2537',
+    'TUROSS HEAD': '2537',
+    'WAMBAN': '2537',
+    'BROOMAN': '2538',
+    'LITTLE FOREST': '2538',
+    'MILTON': '2538',
+    'MOGOOD': '2538',
+    'MORTON': '2538',
+    'PORTERS CREEK': '2538',
+    'BAWLEY POINT': '2539',
+    'BENDALONG': '2539',
+    'BERRINGER LAKE': '2539',
+    'BURRILL LAKE': '2539',
+    'COCKWHY': '2539',
+    'CONJOLA': '2539',
+    'CONJOLA PARK': '2539',
+    'CROOBYAR': '2539',
+    'CUNJURONG POINT': '2539',
+    'DOLPHIN POINT': '2539',
+    'FISHERMANS PARADISE': '2539',
+    'KINGS POINT': '2539',
+    'KIOLOA': '2539',
+    'LAKE CONJOLA': '2539',
+    'LAKE TABOURIE': '2539',
+    'MANYANA': '2539',
+    'MOLLYMOOK': '2539',
+    'MOLLYMOOK BEACH': '2539',
+    'MOUNT KINGIMAN': '2539',
+    'NARRAWALLEE': '2539',
+    'POINTER MOUNTAIN': '2539',
+    'TERMEIL': '2539',
+    'ULLADULLA': '2539',
+    'YADBORO': '2539',
+    'YATTE YATTAH': '2539',
+    'BAMARANG': '2540',
+    'BARRINGELLA': '2540',
+    'BASIN VIEW': '2540',
+    'BEECROFT PENINSULA': '2540',
+    'BERRARA': '2540',
+    'BEWONG': '2540',
+    'BOLONG': '2540',
+    'BOOLIJAH': '2540',
+    'BREAM BEACH': '2540',
+    'BROWNS MOUNTAIN': '2540',
+    'BRUNDEE': '2540',
+    'BUANGLA': '2540',
+    'BURRIER': '2540',
+    'CALLALA BAY': '2540',
+    'CALLALA BEACH': '2540',
+    'CAMBEWARRA': '2540',
+    'CAMBEWARRA VILLAGE': '2540',
+    'COMBERTON': '2540',
+    'COMERONG ISLAND': '2540',
+    'CUDMIRRAH': '2540',
+    'CULBURRA BEACH': '2540',
+    'CURRARONG': '2540',
+    'EROWAL BAY': '2540',
+    'ETTREMA': '2540',
+    'FALLS CREEK': '2540',
+    'GREENWELL POINT': '2540',
+    'HMAS ALBATROSS': '2540',
+    'HMAS CRESWELL': '2540',
+    'HUSKISSON': '2540',
+    'HYAMS BEACH': '2540',
+    'ILLAROO': '2540',
+    'JERRAWANGALA': '2540',
+    'JERVIS BAY': '2540',
+    'KINGHORNE': '2540',
+    'LONGREACH': '2540',
+    'MEROO MEADOW': '2540',
+    'MONDAYONG': '2540',
+    'MOOLLATTOO': '2540',
+    'MUNDAMIA': '2540',
+    'MYOLA': '2540',
+    'NOWRA HILL': '2540',
+    'NOWRA NAVAL PO': '2540',
+    'NUMBAA': '2540',
+    'OLD EROWAL BAY': '2540',
+    'ORIENT POINT': '2540',
+    'PARMA': '2540',
+    'PYREE': '2540',
+    'SANCTUARY POINT': '2540',
+    'ST GEORGES BASIN': '2540',
+    'SUSSEX INLET': '2540',
+    'SWANHAVEN': '2540',
+    'TALLOWAL': '2540',
+    'TAPITALLEE': '2540',
+    'TERARA': '2540',
+    'TOMERONG': '2540',
+    'TULLARWALLA': '2540',
+    'TWELVE MILE PEG': '2540',
+    'VINCENTIA': '2540',
+    'WANDANDIAN': '2540',
+    'WATERSLEIGH': '2540',
+    'WOLLUMBOOLA': '2540',
+    'WOOLLAMIA': '2540',
+    'WORRIGEE': '2540',
+    'WORROWING HEIGHTS': '2540',
+    'WRIGHTS BEACH': '2540',
+    'YALWAL': '2540',
+    'YERRIYONG': '2540',
+    'BANGALEE': '2541',
+    'BOMADERRY': '2541',
+    'NORTH NOWRA': '2541',
+    'NOWRA': '2541',
+    'NOWRA DC': '2541',
+    'NOWRA EAST': '2541',
+    'NOWRA NORTH': '2541',
+    'SOUTH NOWRA': '2541',
+    'WEST NOWRA': '2541',
+    'BELOWRA': '2545',
+    'BODALLA': '2545',
+    'CADGEE': '2545',
+    'EUROBODALLA': '2545',
+    'NERRIGUNDAH': '2545',
+    'POTATO POINT': '2545',
+    'AKOLELE': '2546',
+    'BARRAGGA BAY': '2546',
+    'BERMAGUI': '2546',
+    'CENTRAL TILBA': '2546',
+    'CORUNNA': '2546',
+    'CUTTAGEE': '2546',
+    'DALMENY': '2546',
+    'DIGNAMS CREEK': '2546',
+    'KIANGA': '2546',
+    'MYSTERY BAY': '2546',
+    'NAROOMA': '2546',
+    'NORTH NAROOMA': '2546',
+    'TILBA TILBA': '2546',
+    'TINPOT': '2546',
+    'WADBILLIGA': '2546',
+    'WALLAGA LAKE': '2546',
+    'BERRAMBOOL': '2548',
+    'MERIMBULA': '2548',
+    'MIRADOR': '2548',
+    'TURA BEACH': '2548',
+    'YELLOW PINCH': '2548',
+    'BALD HILLS': '2549',
+    'GREIGS FLAT': '2549',
+    'LOCHIEL': '2549',
+    'MILLINGANDI': '2549',
+    'NETHERCOTE': '2549',
+    'PAMBULA': '2549',
+    'PAMBULA BEACH': '2549',
+    'SOUTH PAMBULA': '2549',
+    'ANGLEDALE': '2550',
+    'BEGA': '2550',
+    'BEMBOKA': '2550',
+    'BLACK RANGE': '2550',
+    'BOURNDA': '2550',
+    'BROGO': '2550',
+    'BUCKAJO': '2550',
+    'BUNGA': '2550',
+    'BURRAGATE': '2550',
+    'CANDELO': '2550',
+    'CHINNOCK': '2550',
+    'COBARGO': '2550',
+    'COOLAGOLITE': '2550',
+    'COOLANGUBRA': '2550',
+    'COOPERS GULLY': '2550',
+    'DEVILS HOLE': '2550',
+    'DOCTOR GEORGE MOUNTAIN': '2550',
+    'FROGS HOLLOW': '2550',
+    'GREENDALE': '2550',
+    'JELLAT JELLAT': '2550',
+    'KALARU': '2550',
+    'KAMERUKA': '2550',
+    'KANOONA': '2550',
+    'MOGAREEKA': '2550',
+    'MOGILLA': '2550',
+    'MORANS CROSSING': '2550',
+    'MUMBULLA MOUNTAIN': '2550',
+    'MURRAH': '2550',
+    'MYRTLE MOUNTAIN': '2550',
+    'NELSON': '2550',
+    'NEW BUILDINGS': '2550',
+    'NUMBUGGA': '2550',
+    'PERICOE': '2550',
+    'QUAAMA': '2550',
+    'REEDY SWAMP': '2550',
+    'ROCKY HALL': '2550',
+    'SOUTH WOLUMLA': '2550',
+    'STONY CREEK': '2550',
+    'TANJA': '2550',
+    'TANTAWANGALO': '2550',
+    'TARRAGANDA': '2550',
+    'TATHRA': '2550',
+    'TOOTHDALE': '2550',
+    'TOWAMBA': '2550',
+    'VERONA': '2550',
+    'WALLAGOOT': '2550',
+    'WANDELLA': '2550',
+    'WAPENGO': '2550',
+    'WOG WOG': '2550',
+    'WOLUMLA': '2550',
+    'WYNDHAM': '2550',
+    'YAMBULLA': '2550',
+    'YANKEES CREEK': '2550',
+    'YOWRIE': '2550',
+    'BOYDTOWN': '2551',
+    'EDEN': '2551',
+    'EDROM': '2551',
+    'GREEN CAPE': '2551',
+    'KIAH': '2551',
+    'NADGEE': '2551',
+    'NARRABARBA': '2551',
+    'NULLICA': '2551',
+    'NUNGATTA': '2551',
+    'NUNGATTA SOUTH': '2551',
+    'TIMBILLICA': '2551',
+    'WONBOYN': '2551',
+    'WONBOYN LAKE': '2551',
+    'WONBOYN NORTH': '2551',
+    'BADGERYS CREEK': '2555',
+    'BRINGELLY': '2556',
+    'CATHERINE FIELD': '2557',
+    'ROSSMORE': '2557',
+    'EAGLE VALE': '2558',
+    'ESCHOL PARK': '2558',
+    'KEARNS': '2558',
+    'BLAIRMOUNT': '2559',
+    'CLAYMORE': '2559',
+    'AIRDS': '2560',
+    'AMBARVALE': '2560',
+    'APPIN': '2560',
+    'BLAIR ATHOL': '2560',
+    'BRADBURY': '2560',
+    'CAMPBELLTOWN': '2560',
+    'CAMPBELLTOWN NORTH': '2560',
+    'CATARACT': '2560',
+    'ENGLORIE PARK': '2560',
+    'GILEAD': '2560',
+    'GLEN ALPINE': '2560',
+    'KENTLYN': '2560',
+    'LEUMEAH': '2560',
+    'MACARTHUR SQUARE': '2560',
+    'ROSEMEADOW': '2560',
+    'RUSE': '2560',
+    'ST HELENS PARK': '2560',
+    'WEDDERBURN': '2560',
+    'WOODBINE': '2560',
+    'MENANGLE PARK': '2563',
+    'GLENQUARIE': '2564',
+    'MACQUARIE FIELDS': '2564',
+    'DENHAM COURT': '2565',
+    'INGLEBURN': '2565',
+    'MACQUARIE LINKS': '2565',
+    'BOW BOWING': '2566',
+    'MINTO': '2566',
+    'MINTO DC': '2566',
+    'MINTO HEIGHTS': '2566',
+    'RABY': '2566',
+    'ST ANDREWS': '2566',
+    'VARROVILLE': '2566',
+    'CURRANS HILL': '2567',
+    'HARRINGTON PARK': '2567',
+    'MOUNT ANNAN': '2567',
+    'NARELLAN': '2567',
+    'NARELLAN DC': '2567',
+    'NARELLAN VALE': '2567',
+    'SMEATON GRANGE': '2567',
+    'MENANGLE': '2568',
+    'DOUGLAS PARK': '2569',
+    'BELIMBLA PARK': '2570',
+    'BICKLEY VALE': '2570',
+    'BROWNLOW HILL': '2570',
+    'CAMDEN': '2570',
+    'CAMDEN PARK': '2570',
+    'CAMDEN SOUTH': '2570',
+    'CAWDOR': '2570',
+    'COBBITTY': '2570',
+    'ELLIS LANE': '2570',
+    'GLENMORE': '2570',
+    'GRASMERE': '2570',
+    'KIRKHAM': '2570',
+    'MOUNT HUNTER': '2570',
+    'NATTAI': '2570',
+    'OAKDALE': '2570',
+    'ORAN PARK': '2570',
+    'ORANGEVILLE': '2570',
+    'SPRING FARM': '2570',
+    'THE OAKS': '2570',
+    'THERESA PARK': '2570',
+    'WEROMBI': '2570',
+    'BUXTON': '2571',
+    'COURIDJAH': '2571',
+    'MALDON': '2571',
+    'MOWBRAY PARK': '2571',
+    'PICTON': '2571',
+    'RAZORBACK': '2571',
+    'LAKESLAND': '2572',
+    'THIRLMERE': '2572',
+    'TAHMOOR': '2573',
+    'AVON': '2574',
+    'BARGO': '2574',
+    'PHEASANTS NEST': '2574',
+    'YANDERRA': '2574',
+    'ALPINE': '2575',
+    'AYLMERTON': '2575',
+    'BRAEMAR': '2575',
+    'BULLIO': '2575',
+    'COLO VALE': '2575',
+    'HIGH RANGE': '2575',
+    'HILL TOP': '2575',
+    'JOADJA': '2575',
+    'MANDEMAR': '2575',
+    'MITTAGONG': '2575',
+    'MOUNT LINDSEY': '2575',
+    'WATTLE RIDGE': '2575',
+    'WELBY': '2575',
+    'YERRINBOOL': '2575',
+    'BONG BONG': '2576',
+    'BOWRAL': '2576',
+    'BURRADOO': '2576',
+    'EAST BOWRAL': '2576',
+    'EAST KANGALOON': '2576',
+    'GLENQUARRY': '2576',
+    'KANGALOON': '2576',
+    'AVOCA': '2577',
+    'BANGADILLY': '2577',
+    'BARREN GROUNDS': '2577',
+    'BARRENGARRY': '2577',
+    'BEAUMONT': '2577',
+    'BELANGLO': '2577',
+    'BERRIMA': '2577',
+    'BUDGONG': '2577',
+    'BURRAWANG': '2577',
+    'CALWALLA': '2577',
+    'CANYONLEIGH': '2577',
+    'CARRINGTON FALLS': '2577',
+    'FITZROY FALLS': '2577',
+    'KANGAROO VALLEY': '2577',
+    'KNIGHTS HILL': '2577',
+    'MACQUARIE PASS': '2577',
+    'MANCHESTER SQUARE': '2577',
+    'MEDWAY': '2577',
+    'MERYLA': '2577',
+    'MOSS VALE': '2577',
+    'MOUNT MURRAY': '2577',
+    'MYRA VALE': '2577',
+    'NEW BERRIMA': '2577',
+    'PADDYS RIVER': '2577',
+    'PHEASANT GROUND': '2577',
+    'RED ROCKS': '2577',
+    'ROBERTSON': '2577',
+    'SUTTON FOREST': '2577',
+    'UPPER KANGAROO RIVER': '2577',
+    'UPPER KANGAROO VALLEY': '2577',
+    'WERAI': '2577',
+    'WILDES MEADOW': '2577',
+    'YARRUNGA': '2577',
+    'BUNDANOON': '2578',
+    'BIG HILL': '2579',
+    'BRAYTON': '2579',
+    'EXETER': '2579',
+    'MARULAN': '2579',
+    'MARULAN SOUTH': '2579',
+    'TALLONG': '2579',
+    'WINGELLO': '2579',
+    'BANNABY': '2580',
+    'BANNISTER': '2580',
+    'BAW BAW': '2580',
+    'BOXERS CREEK': '2580',
+    'BRISBANE GROVE': '2580',
+    'BUNGONIA': '2580',
+    'CARRICK': '2580',
+    'CHATSBURY': '2580',
+    'CURRAWANG': '2580',
+    'GOLSPIE': '2580',
+    'GOULBURN': '2580',
+    'GOULBURN DC': '2580',
+    'GOULBURN NORTH': '2580',
+    'GREENWICH PARK': '2580',
+    'GUNDARY': '2580',
+    'JERRONG': '2580',
+    'KINGSDALE': '2580',
+    'LAKE BATHURST': '2580',
+    'LEIGHWOOD': '2580',
+    'LOWER BORO': '2580',
+    'MCALISTER': '2580',
+    'MIDDLE ARM': '2580',
+    'MOUNT FAIRY': '2580',
+    'MOUNT RAE': '2580',
+    'MUMMEL': '2580',
+    'MYRTLEVILLE': '2580',
+    'PARKESBOURNE': '2580',
+    'POMEROY': '2580',
+    'QUIALIGO': '2580',
+    'RICHLANDS': '2580',
+    'ROSLYN': '2580',
+    'RUNOWATERS': '2580',
+    'TARAGO': '2580',
+    'TARALGA': '2580',
+    'TARLO': '2580',
+    'TIRRANNAVILLE': '2580',
+    'TOWRANG': '2580',
+    'WAYO': '2580',
+    'WIARBOROUGH': '2580',
+    'WINDELLAMA': '2580',
+    'WOMBEYAN CAVES': '2580',
+    'WOODHOUSELEE': '2580',
+    'WOWAGIN': '2580',
+    'YALBRAITH': '2580',
+    'YARRA': '2580',
+    'BELLMOUNT FOREST': '2581',
+    'BEVENDALE': '2581',
+    'BIALA': '2581',
+    'BLAKNEY CREEK': '2581',
+    'BREADALBANE': '2581',
+    'COLLECTOR': '2581',
+    'CULLERIN': '2581',
+    'DALTON': '2581',
+    'GUNNING': '2581',
+    'GURRUNDAH': '2581',
+    'LADE VALE': '2581',
+    'LAKE GEORGE': '2581',
+    'LERIDA': '2581',
+    'MERRILL': '2581',
+    'OOLONG': '2581',
+    'WOLLOGORANG': '2581',
+    'BANGO': '2582',
+    'BERREMANGRA': '2582',
+    'BOAMBOLO': '2582',
+    'BOOKHAM': '2582',
+    'BOWNING': '2582',
+    'BURRINJUCK': '2582',
+    'CAVAN': '2582',
+    'GOOD HOPE': '2582',
+    'JEIR': '2582',
+    'JERRAWA': '2582',
+    'KANGIARA': '2582',
+    'LAVERSTOCK': '2582',
+    'MARCHMONT': '2582',
+    'MULLION': '2582',
+    'MURRUMBATEMAN': '2582',
+    'NANANGROE': '2582',
+    'NARRANGULLEN': '2582',
+    'WEE JASPER': '2582',
+    'WOOLGARLO': '2582',
+    'YASS': '2582',
+    'YASS RIVER': '2582',
+    'BIGGA': '2583',
+    'BINDA': '2583',
+    'BLANKET FLAT': '2583',
+    'BROOKLANDS': '2583',
+    'COTTAWALLA': '2583',
+    'CROOKED CORNER': '2583',
+    'CROOKWELL': '2583',
+    'FULLERTON': '2583',
+    'GLENERIN': '2583',
+    'GRABBEN GULLEN': '2583',
+    'GRABINE': '2583',
+    'GREENMANTLE': '2583',
+    'HADLEY': '2583',
+    'JUNCTION POINT': '2583',
+    'KEMPTON': '2583',
+    'KIALLA': '2583',
+    'LAGGAN': '2583',
+    'LIMERICK': '2583',
+    'LOST RIVER': '2583',
+    'MULGOWRIE': '2583',
+    'NARRAWA': '2583',
+    'PEELWOOD': '2583',
+    'PEJAR': '2583',
+    'RUGBY': '2583',
+    'THIRD CREEK': '2583',
+    'TUENA': '2583',
+    'WHEEO': '2583',
+    'BINALONG': '2584',
+    'GALONG': '2585',
+    'BOOROWA': '2586',
+    'FROGMORE': '2586',
+    'GOBA CREEK': '2586',
+    'GODFREYS CREEK': '2586',
+    'KENYU': '2586',
+    'MURRINGO': '2586',
+    'REIDS FLAT': '2586',
+    'RYE PARK': '2586',
+    'TAYLORS FLAT': '2586',
+    'AURVILLE': '2587',
+    'CUNNINGAR': '2587',
+    'DEMONDRILLE': '2587',
+    'GARANGULA': '2587',
+    'HARDEN': '2587',
+    'KINGSVALE': '2587',
+    'MCMAHONS REEF': '2587',
+    'MURRUMBURRAH': '2587',
+    'NUBBA': '2587',
+    'PRUNEVALE': '2587',
+    'WOMBAT': '2587',
+    'WALLENDBEEN': '2588',
+    'BETHUNGRA': '2590',
+    'COOTAMUNDRA': '2590',
+    'ILLABO': '2590',
+    'BERTHONG': '2594',
+    'BRIBBAREE': '2594',
+    'BULLA CREEK': '2594',
+    'BURRANGONG': '2594',
+    'KIKIAMAH': '2594',
+    'MAIMURU': '2594',
+    'MEMAGONG': '2594',
+    'MILVALE': '2594',
+    'MONTEAGLE': '2594',
+    'THUDDUNGRA': '2594',
+    'TUBBUL': '2594',
+    'WEEDALLION': '2594',
+    'YOUNG': '2594',
+    'BARTON': '2600',
+    'CANBERRA': '2600',
+    'CAPITAL HILL': '2600',
+    'DEAKIN': '2600',
+    'DEAKIN WEST': '2600',
+    'DUNTROON': '2600',
+    'HARMAN': '2600',
+    'HMAS HARMAN': '2600',
+    'PARKES': '2600',
+    'RUSSELL': '2600',
+    'RUSSELL HILL': '2600',
+    'YARRALUMLA': '2600',
+    'ACTON': '2601',
+    'CITY': '2601',
+    'AINSLIE': '2602',
+    'DICKSON': '2602',
+    'DOWNER': '2602',
+    'HACKETT': '2602',
+    'LYNEHAM': '2602',
+    "O'CONNOR": '2602',
+    'WATSON': '2602',
+    'FORREST': '2603',
+    'GRIFFITH': '2603',
+    'MANUKA': '2603',
+    'CAUSEWAY': '2604',
+    'KINGSTON': '2604',
+    'NARRABUNDAH': '2604',
+    'CURTIN': '2605',
+    'GARRAN': '2605',
+    'HUGHES': '2605',
+    "O'MALLEY": '2606',
+    'PHILLIP': '2606',
+    'PHILLIP DC': '2606',
+    'SWINGER HILL': '2606',
+    'WODEN': '2606',
+    'FARRER': '2607',
+    'ISAACS': '2607',
+    'MAWSON': '2607',
+    'PEARCE': '2607',
+    'TORRENS': '2607',
+    'CANBERRA INTERNATIONAL AIRPORT': '2609',
+    'FYSHWICK': '2609',
+    'MAJURA': '2609',
+    'PIALLIGO': '2609',
+    'SYMONSTON': '2609',
+    'BIMBERI': '2611',
+    'BRINDABELLA': '2611',
+    'CHAPMAN': '2611',
+    'COOLEMAN': '2611',
+    'DUFFY': '2611',
+    'FISHER': '2611',
+    'HOLDER': '2611',
+    'MOUNT STROMLO': '2611',
+    'PIERCES CREEK': '2611',
+    'RIVETT': '2611',
+    'STIRLING': '2611',
+    'URIARRA': '2611',
+    'URIARRA FOREST': '2611',
+    'WARAMANGA': '2611',
+    'WESTON CREEK': '2611',
+    'BRADDON': '2612',
+    'CAMPBELL': '2612',
+    'REID': '2612',
+    'TURNER': '2612',
+    'ARANDA': '2614',
+    'COOK': '2614',
+    'HAWKER': '2614',
+    'JAMISON CENTRE': '2614',
+    'MACQUARIE': '2614',
+    'PAGE': '2614',
+    'SCULLIN': '2614',
+    'WEETANGERA': '2614',
+    'CHARNWOOD': '2615',
+    'DUNLOP': '2615',
+    'FLOREY': '2615',
+    'FRASER': '2615',
+    'HIGGINS': '2615',
+    'HOLT': '2615',
+    'KIPPAX': '2615',
+    'LATHAM': '2615',
+    'MACGREGOR': '2615',
+    'MELBA': '2615',
+    'SPENCE': '2615',
+    'BELCONNEN': '2617',
+    'BELCONNEN DC': '2617',
+    'BRUCE': '2617',
+    'EVATT': '2617',
+    'GIRALANG': '2617',
+    'KALEEN': '2617',
+    'LAWSON': '2617',
+    'MCKELLAR': '2617',
+    'UNIVERSITY OF CANBERRA': '2617',
+    'HALL': '2618',
+    'NANIMA': '2618',
+    'SPRINGRANGE': '2618',
+    'WALLAROO': '2618',
+    'JERRABOMBERRA': '2619',
+    'BURRA': '2620',
+    'CARWOOLA': '2620',
+    'CLEAR RANGE': '2620',
+    'CRESTWOOD': '2620',
+    'DODSWORTH': '2620',
+    'ENVIRONA': '2620',
+    'GOOGONG': '2620',
+    'GREENLEIGH': '2620',
+    'GUNDAROO': '2620',
+    'HUME': '2620',
+    'KARABAR': '2620',
+    'KOWEN FOREST': '2620',
+    'LETCHWORTH': '2620',
+    'MICHELAGO': '2620',
+    'OAKS ESTATE': '2620',
+    'QUEANBEYAN': '2620',
+    'QUEANBEYAN DC': '2620',
+    'QUEANBEYAN EAST': '2620',
+    'QUEANBEYAN WEST': '2620',
+    'ROYALLA': '2620',
+    'SUTTON': '2620',
+    'THARWA': '2620',
+    'THE ANGLE': '2620',
+    'THE RIDGEWAY': '2620',
+    'TINDERRY': '2620',
+    'TOP NAAS': '2620',
+    'TRALEE': '2620',
+    'URILA': '2620',
+    'WAMBOIN': '2620',
+    'WILLIAMSDALE': '2620',
+    'YARROW': '2620',
+    'ANEMBO': '2621',
+    'BUNGENDORE': '2621',
+    'BYWONG': '2621',
+    'FORBES CREEK': '2621',
+    'HOSKINSTOWN': '2621',
+    'PRIMROSE VALLEY': '2621',
+    'ROSSI': '2621',
+    'ARALUEN NORTH': '2622',
+    'BALLALABA': '2622',
+    'BENDOURA': '2622',
+    'BERLANG': '2622',
+    'BOMBAY': '2622',
+    'BORO': '2622',
+    'BRAIDWOOD': '2622',
+    'BUDAWANG': '2622',
+    'BULEE': '2622',
+    'CHARLEYS FOREST': '2622',
+    'COOLUMBURRA': '2622',
+    'CORANG': '2622',
+    'DURRAN DURRA': '2622',
+    'ENDRICK': '2622',
+    'FARRINGDON': '2622',
+    'GUNDILLION': '2622',
+    'HAROLDS CROSS': '2622',
+    'HEREFORD HALL': '2622',
+    'JEMBAICUMBENE': '2622',
+    'JERRABATTGULLA': '2622',
+    'JINDEN': '2622',
+    'JINGERA': '2622',
+    'KINDERVALE': '2622',
+    'KRAWARREE': '2622',
+    'LARBERT': '2622',
+    'MAJORS CREEK': '2622',
+    'MANAR': '2622',
+    'MARLOWE': '2622',
+    'MERRICUMBENE': '2622',
+    'MONGA': '2622',
+    'MONGARLOWE': '2622',
+    'MULLOON': '2622',
+    'MURRENGENBURG': '2622',
+    'NERINGLA': '2622',
+    'NERRIGA': '2622',
+    'NORTHANGERA': '2622',
+    'OALLEN': '2622',
+    'PALERANG': '2622',
+    'QUIERA': '2622',
+    'REIDSDALE': '2622',
+    'SASSAFRAS': '2622',
+    'SNOWBALL': '2622',
+    'ST GEORGE': '2622',
+    'TIANJARA': '2622',
+    'TOLWONG': '2622',
+    'TOMBOYE': '2622',
+    'TOUGA': '2622',
+    'WARRI': '2622',
+    'WYANBENE': '2622',
+    'CAPTAINS FLAT': '2623',
+    'KOSCIUSZKO': '2624',
+    'PERISHER VALLEY': '2624',
+    'THREDBO': '2625',
+    'THREDBO VILLAGE': '2625',
+    'BREDBO': '2626',
+    'BUMBALONG': '2626',
+    'COLINTON': '2626',
+    'CRACKENBACK': '2627',
+    'EAST JINDABYNE': '2627',
+    'GROSSES PLAIN': '2627',
+    'GUNGARLIN': '2627',
+    'INGEBIRAH': '2627',
+    'JINDABYNE': '2627',
+    'KALKITE': '2627',
+    'MOONBAH': '2627',
+    'PILOT WILDERNESS': '2627',
+    'AVONSIDE': '2628',
+    'BELOKA': '2628',
+    'BERRIDALE': '2628',
+    'BYADBO WILDERNESS': '2628',
+    'COOTRALANTRA': '2628',
+    'DALGETY': '2628',
+    'EUCUMBENE': '2628',
+    'EUCUMBENE COVE': '2628',
+    'NIMMO': '2628',
+    'NUMBLA VALE': '2628',
+    'PAUPONG': '2628',
+    'ROCKY PLAIN': '2628',
+    'SNOWY PLAIN': '2628',
+    'ADAMINABY': '2629',
+    'ANGLERS REACH': '2629',
+    'BOLARO': '2629',
+    'CABRAMURRA': '2629',
+    'OLD ADAMINABY': '2629',
+    'PROVIDENCE PORTAL': '2629',
+    'TANTANGARA': '2629',
+    'YAOUK': '2629',
+    'ARABLE': '2630',
+    'BADJA': '2630',
+    'BILLILINGRA': '2630',
+    'BINJURA': '2630',
+    'BOBUNDARA': '2630',
+    'BUCKENDERRA': '2630',
+    'BUNGARBY': '2630',
+    'BUNYAN': '2630',
+    'CARLAMINDA': '2630',
+    'CHAKOLA': '2630',
+    'COOLRINGDON': '2630',
+    'COOMA': '2630',
+    'COOMA NORTH': '2630',
+    'COUNTEGANY': '2630',
+    'DAIRYMANS PLAINS': '2630',
+    'DANGELONG': '2630',
+    'DRY PLAIN': '2630',
+    'FRYING PAN': '2630',
+    'GLEN FERGUS': '2630',
+    'IRONMUNGY': '2630',
+    'JERANGLE': '2630',
+    'JIMENBUEN': '2630',
+    'MAFFRA': '2630',
+    'MIDDLE FLAT': '2630',
+    'MIDDLINGBANK': '2630',
+    'MURRUMBUCCA': '2630',
+    'MYALLA': '2630',
+    'NUMERALLA': '2630',
+    'PEAK VIEW': '2630',
+    'PINE VALLEY': '2630',
+    'POLO FLAT': '2630',
+    'RHINE FALLS': '2630',
+    'ROCK FLAT': '2630',
+    'SHANNONS FLAT': '2630',
+    'THE BROTHERS': '2630',
+    'TUROSS': '2630',
+    'WAMBROOK': '2630',
+    'ANDO': '2631',
+    'BOCO': '2631',
+    'CREEWAH': '2631',
+    'GLEN ALLEN': '2631',
+    'HOLTS FLAT': '2631',
+    'JINCUMBILLY': '2631',
+    'KYBEYAN': '2631',
+    'MOUNT COOPER': '2631',
+    'NIMMITABEL': '2631',
+    'STEEPLE FLAT': '2631',
+    'WINIFRED': '2631',
+    'BIBBENLUKE': '2632',
+    'BOMBALA': '2632',
+    'BONDI FOREST': '2632',
+    'BUKALONG': '2632',
+    'CAMBALONG': '2632',
+    'CATHCART': '2632',
+    'COOLUMBOOKA': '2632',
+    'CRAIGIE': '2632',
+    'GUNNINGRAH': '2632',
+    'LORDS HILL': '2632',
+    'MERRIANGAAH': '2632',
+    'MILA': '2632',
+    'MOUNT DARRAGH': '2632',
+    'PALARANG': '2632',
+    'QUIDONG': '2632',
+    'ROCKTON': '2632',
+    'ROSEMEATH': '2632',
+    'CORROWONG': '2633',
+    'DELEGATE': '2633',
+    'TOMBONG': '2633',
+    'ALBURY': '2640',
+    'BUNGOWANNAH': '2640',
+    'EAST ALBURY': '2640',
+    'ETTAMOGAH': '2640',
+    'GLENROY': '2640',
+    'LAKE HUME VILLAGE': '2640',
+    'LAVINGTON DC': '2640',
+    'MOORWATHA': '2640',
+    'NORTH ALBURY': '2640',
+    'OURNIE': '2640',
+    'SOUTH ALBURY': '2640',
+    'SPLITTERS CREEK': '2640',
+    'TABLE TOP': '2640',
+    'TALMALMO': '2640',
+    'THURGOONA': '2640',
+    'WEST ALBURY': '2640',
+    'WIRLINGA': '2640',
+    'WYMAH': '2640',
+    'HAMILTON VALLEY': '2641',
+    'LAVINGTON': '2641',
+    'SPRINGDALE HEIGHTS': '2641',
+    'BIDGEEMIA': '2642',
+    'BROCKLESBY': '2642',
+    'BURRUMBUTTOCK': '2642',
+    'GEEHI': '2642',
+    'GEROGERY': '2642',
+    'GLENELLEN': '2642',
+    'GREG GREG': '2642',
+    'INDI': '2642',
+    'JAGUMBA': '2642',
+    'JAGUNGAL WILDERNESS': '2642',
+    'JINDERA': '2642',
+    'JINGELLIC': '2642',
+    'KHANCOBAN': '2642',
+    'MURRAY GORGE': '2642',
+    'RAND': '2642',
+    'TOOMA': '2642',
+    'WALBUNDRIE': '2642',
+    'WELAREGANG': '2642',
+    'WRATHALL': '2642',
+    'YERONG CREEK': '2642',
+    'HOWLONG': '2643',
+    'BOWNA': '2644',
+    'COPPABELLA': '2644',
+    'HOLBROOK': '2644',
+    'LANKEYS CREEK': '2644',
+    'LITTLE BILLABONG': '2644',
+    'MOUNTAIN CREEK': '2644',
+    'MULLENGANDRA': '2644',
+    'WANTAGONG': '2644',
+    'WOOMARGAMA': '2644',
+    'YARARA': '2644',
+    'COONONG': '2645',
+    'CULLIVEL': '2645',
+    'URANA': '2645',
+    'YULUMA': '2645',
+    'BALLDALE': '2646',
+    'BULL PLAIN': '2646',
+    'BURAJA': '2646',
+    'COADS TANK': '2646',
+    'COLLENDINA': '2646',
+    'COREEN': '2646',
+    'COROWA': '2646',
+    'DAYSDALE': '2646',
+    'GOOMBARGANA': '2646',
+    'HOPEFIELD': '2646',
+    'LOWESDALE': '2646',
+    'MERTON VALE': '2646',
+    'NYORA': '2646',
+    'OAKLANDS': '2646',
+    'REDLANDS': '2646',
+    'RENNIE': '2646',
+    'RINGWOOD': '2646',
+    'SANGER': '2646',
+    'SAVERNAKE': '2646',
+    'MULWALA': '2647',
+    'ANABRANCH': '2648',
+    'BOEILL CREEK': '2648',
+    'CAL LAL': '2648',
+    'CURLWAA': '2648',
+    'MOURQUONG': '2648',
+    'PALINYEWAH': '2648',
+    'PAN BAN': '2648',
+    'POMONA': '2648',
+    'POONCARIE': '2648',
+    'RUFUS RIVER': '2648',
+    'SCOTIA': '2648',
+    'WENTWORTH': '2648',
+    'LAUREL HILL': '2649',
+    'NURENMERENMONG': '2649',
+    'ALFREDTOWN': '2650',
+    'ASHMONT': '2650',
+    'BELFRAYDEN': '2650',
+    'BIG SPRINGS': '2650',
+    'BOMEN': '2650',
+    'BOOK BOOK': '2650',
+    'BOOROOMA': '2650',
+    'BORAMBOLA': '2650',
+    'BOURKELANDS': '2650',
+    'BRUCEDALE': '2650',
+    'BULGARY': '2650',
+    'BURRANDANA': '2650',
+    'CARABOST': '2650',
+    'CARTWRIGHTS HILL': '2650',
+    'COLLINGULLIE': '2650',
+    'COOKARDINIA': '2650',
+    'CURRAWANANNA': '2650',
+    'CURRAWARNA': '2650',
+    'DOWNSIDE': '2650',
+    'EAST WAGGA WAGGA': '2650',
+    'ESTELLA': '2650',
+    'EUBERTA': '2650',
+    'EUNANOREENYA': '2650',
+    'GALORE': '2650',
+    'GELSTON PARK': '2650',
+    'GLENFIELD PARK': '2650',
+    'GOBBAGOMBALIN': '2650',
+    'GREGADOO': '2650',
+    'HAREFIELD': '2650',
+    'KOORINGAL': '2650',
+    'KYEAMBA': '2650',
+    'LAKE ALBERT': '2650',
+    'LLOYD': '2650',
+    'MAXWELL': '2650',
+    'MOORONG': '2650',
+    'MOUNT AUSTIN': '2650',
+    'NORTH WAGGA WAGGA': '2650',
+    'OBERNE CREEK': '2650',
+    'OURA': '2650',
+    'PULLETOP': '2650',
+    'ROWAN': '2650',
+    'SAN ISIDORE': '2650',
+    'SOUTH WAGGA WAGGA': '2650',
+    'SPRINGVALE': '2650',
+    'TATTON': '2650',
+    'TOLLAND': '2650',
+    'TURVEY PARK': '2650',
+    'WAGGA WAGGA': '2650',
+    'WAGGA WAGGA BC': '2650',
+    'WALLACETOWN': '2650',
+    'WANTABADGERY': '2650',
+    'YARRAGUNDRY': '2650',
+    'YATHELLA': '2650',
+    'FOREST HILL': '2651',
+    'WAGGA WAGGA RAAF': '2651',
+    'BOORGA': '2652',
+    'BOREE CREEK': '2652',
+    'GOOLGOWI': '2652',
+    'GRONG GRONG': '2652',
+    'GUMLY GUMLY': '2652',
+    'HUMULA': '2652',
+    'LADYSMITH': '2652',
+    'MANGOPLAH': '2652',
+    'MARRAR': '2652',
+    'MATONG': '2652',
+    'MERRIWAGGA': '2652',
+    'MURRULEBALE': '2652',
+    'OLD JUNEE': '2652',
+    'TABBITA': '2652',
+    'TARCUTTA': '2652',
+    'URANQUINTY': '2652',
+    'COURABYRA': '2653',
+    'MANNUS': '2653',
+    'MARAGLE': '2653',
+    'MUNDEROO': '2653',
+    'TARADALE': '2653',
+    'TUMBARUMBA': '2653',
+    'WILLIGOBUNG': '2653',
+    'BIRDLIP': '2655',
+    'FRENCH PARK': '2655',
+    'KUBURA': '2655',
+    'THE ROCK': '2655',
+    'TOOTOOL': '2655',
+    'BROOKDALE': '2656',
+    'BROOKONG': '2656',
+    'FARGUNYAH': '2656',
+    'LOCKHART': '2656',
+    'MILBRULONG': '2656',
+    'OSBORNE': '2656',
+    'URANGELINE': '2656',
+    'URANGELINE EAST': '2656',
+    'GRUBBEN': '2658',
+    'HENTY': '2658',
+    'MUNYABLA': '2658',
+    'PLEASANT HILLS': '2658',
+    'RYAN': '2658',
+    'ALMA PARK': '2659',
+    'WALLA WALLA': '2659',
+    'CARNSDALE': '2660',
+    'CULCAIRN': '2660',
+    'KAPOOKA': '2661',
+    'BUNDURE': '2663',
+    'COOBA': '2663',
+    'COWABBIE': '2663',
+    'ERIN VALE': '2663',
+    'EURONGILLY': '2663',
+    'JUNEE': '2663',
+    'LANDERVALE': '2663',
+    'MARINNA': '2663',
+    'WANTIOOL': '2663',
+    'ARDLETHAN': '2665',
+    'ARIAH PARK': '2665',
+    'BARELLAN': '2665',
+    'BECKOM': '2665',
+    'BECTRIC': '2665',
+    'BINYA': '2665',
+    'KAMARAH': '2665',
+    'MIRROOL': '2665',
+    'MOOMBOOLDOOL': '2665',
+    'MOUNT CRYSTAL': '2665',
+    'QUANDARY': '2665',
+    'WALLEROOBIE': '2665',
+    'COMBANING': '2666',
+    'DIRNASEER': '2666',
+    'GIDGINBUNG': '2666',
+    'GROGAN': '2666',
+    'JUNEE REEFS': '2666',
+    'MIMOSA': '2666',
+    'MORANGARELL': '2666',
+    'NARRABURRA': '2666',
+    'PUCAWAN': '2666',
+    'REEFTON': '2666',
+    'SEBASTOPOL': '2666',
+    'SPRINGDALE': '2666',
+    'TEMORA': '2666',
+    'TRUNGLEY HALL': '2666',
+    'BARMEDMAN': '2668',
+    'BYGALORIE': '2669',
+    'ERIGOLIA': '2669',
+    'GIRRAL': '2669',
+    'KIKOIRA': '2669',
+    'MELBERGEN': '2669',
+    'NARADHAN': '2669',
+    'RANKINS SPRINGS': '2669',
+    'TALLIMBA': '2669',
+    'TULLIBIGEAL': '2669',
+    'UNGARIE': '2669',
+    'WEETHALLE': '2669',
+    'WEJA': '2669',
+    'YADDRA': '2669',
+    'ALLEENA': '2671',
+    'BURCHER': '2671',
+    'LAKE COWAL': '2671',
+    'NORTH YALGOGRIN': '2671',
+    'WEST WYALONG': '2671',
+    'WYALONG': '2671',
+    'BURGOONEY': '2672',
+    'CURLEW WATERS': '2672',
+    'LAKE CARGELLIGO': '2672',
+    'MURRIN BRIDGE': '2672',
+    'WARGAMBEGAL': '2672',
+    'HILLSTON': '2675',
+    'LAKE BREWSTER': '2675',
+    'MONIA GAP': '2675',
+    'ROTO': '2675',
+    'WALLANTHERY': '2675',
+    'BEELBANGERA': '2680',
+    'BENEREMBAH': '2680',
+    'BILBUL': '2680',
+    'GRIFFITH DC': '2680',
+    'GRIFFITH EAST': '2680',
+    'HANWOOD': '2680',
+    'KOOBA': '2680',
+    'LAKE WYANGAN': '2680',
+    'NERICON': '2680',
+    'THARBOGANG': '2680',
+    'WARBURN': '2680',
+    'WARRAWIDGEE': '2680',
+    'WIDGELLI': '2680',
+    'WILLBRIGGIE': '2680',
+    'YOOGALI': '2680',
+    'MYALL PARK': '2681',
+    'YENDA': '2681',
+    'BIRREGO': '2700',
+    'COLINROOBIE': '2700',
+    'COROBIMILLA': '2700',
+    'CUDGEL': '2700',
+    'EUROLEY': '2700',
+    'FAITHFULL': '2700',
+    'GILLENBAH': '2700',
+    'KYWONG': '2700',
+    'MORUNDAH': '2700',
+    'NARRANDERA': '2700',
+    'PAYNTERS SIDING': '2700',
+    'SANDIGO': '2700',
+    'WIDGIEWA': '2700',
+    'BERRY JERRY': '2701',
+    'COOLAMON': '2701',
+    'METHUL': '2701',
+    'RANNOCK': '2701',
+    'TOOYAL': '2701',
+    'GANMAIN': '2702',
+    'YANCO': '2703',
+    'BROBENAH': '2705',
+    'CALORAFIELD': '2705',
+    'CORBIE HILL': '2705',
+    'GOGELDRIE': '2705',
+    'LEETON': '2705',
+    'MERUNGLE HILL': '2705',
+    'MURRAMI': '2705',
+    'STANBRIDGE': '2705',
+    'WAMOON': '2705',
+    'WHITTON': '2705',
+    'DARLINGTON POINT': '2706',
+    'ARGOON': '2707',
+    'COLEAMBALLY': '2707',
+    'BARRATTA': '2710',
+    'BENARCA': '2710',
+    'BIRGANBIGIL': '2710',
+    'BOOROORBAN': '2710',
+    'BRASSI': '2710',
+    'BULLATALE': '2710',
+    'CALDWELL': '2710',
+    'CALIMO': '2710',
+    'CONARGO': '2710',
+    'COREE': '2710',
+    'CORNALLA': '2710',
+    'DENILIQUIN': '2710',
+    'GULPA': '2710',
+    'HARTWOOD': '2710',
+    'HILL PLAIN': '2710',
+    'LINDIFFERON': '2710',
+    'MATHOURA': '2710',
+    'MAYRUNG': '2710',
+    'MOIRA': '2710',
+    'MOONAHCULLAH': '2710',
+    'MOONBRIA': '2710',
+    'MORAGO': '2710',
+    'PRETTY PINE': '2710',
+    'STEAM PLAINS': '2710',
+    'STUD PARK': '2710',
+    'WAKOOL': '2710',
+    'WANDOOK': '2710',
+    'WANGANELLA': '2710',
+    'WARRAGOON': '2710',
+    'WILLURAH': '2710',
+    'YALLAKOOL': '2710',
+    'BOOLIGAL': '2711',
+    'CARRATHOOL': '2711',
+    'CLARE': '2711',
+    'CORRONG': '2711',
+    'GUNBAR': '2711',
+    'HAY': '2711',
+    'HAY SOUTH': '2711',
+    'KERI KERI': '2711',
+    'MAUDE': '2711',
+    'ONE TREE': '2711',
+    'OXLEY': '2711',
+    'WAUGORAH': '2711',
+    'YANGA': '2711',
+    'BERRIGAN': '2712',
+    'BOOMANOOMANA': '2712',
+    'BLIGHTY': '2713',
+    'FINLEY': '2713',
+    'LOGIE BRAE': '2713',
+    'MYRTLE PARK': '2713',
+    'ARATULA': '2714',
+    'PINE LODGE': '2714',
+    'TOCUMWAL': '2714',
+    'TUPPAL': '2714',
+    'ARUMPO': '2715',
+    'BALRANALD': '2715',
+    'HATFIELD': '2715',
+    'MUNGO': '2715',
+    'PENARIE': '2715',
+    'COREE SOUTH': '2716',
+    'FOUR CORNERS': '2716',
+    'GALA VALE': '2716',
+    'JERILDERIE': '2716',
+    'MABINS WELL': '2716',
+    'MAIRJIMMY': '2716',
+    'DARETON': '2717',
+    'ARGALONG': '2720',
+    'BLOWERING': '2720',
+    'BOGONG PEAKS WILDERNESS': '2720',
+    'BOMBOWLEE': '2720',
+    'BOMBOWLEE CREEK': '2720',
+    'BUDDONG': '2720',
+    'COURAGAGO': '2720',
+    'GADARA': '2720',
+    'GILMORE': '2720',
+    'GOCUP': '2720',
+    'GOOBARRAGANDRA': '2720',
+    'JONES BRIDGE': '2720',
+    'KILLIMICAT': '2720',
+    'LACMALAC': '2720',
+    'LITTLE RIVER': '2720',
+    'MINJARY': '2720',
+    'MUNDONGO': '2720',
+    'PINBEYAN': '2720',
+    'TALBINGO': '2720',
+    'TUMORRAMA': '2720',
+    'TUMUT': '2720',
+    'TUMUT PLAINS': '2720',
+    'WEREBOLDERA': '2720',
+    'WERMATONG': '2720',
+    'WINDOWIE': '2720',
+    'WYANGLE': '2720',
+    'YARRANGOBILLY': '2720',
+    'BLAND': '2721',
+    'QUANDIALLA': '2721',
+    'BONGALONG': '2722',
+    'BONGONGALONG': '2722',
+    'BRUNGLE': '2722',
+    'BRUNGLE CREEK': '2722',
+    'BURRA CREEK': '2722',
+    'DARBALARA': '2722',
+    'EDWARDSTOWN': '2722',
+    'GUNDAGAI': '2722',
+    'JACKALASS': '2722',
+    'JONES CREEK': '2722',
+    'MUTTAMA': '2722',
+    'NANGUS': '2722',
+    'RENO': '2722',
+    'SOUTH GUNDAGAI': '2722',
+    'TARRABANDRA': '2722',
+    'WAGRAGOBILLY': '2722',
+    'WILLIE PLOMA': '2722',
+    'STOCKINBINGAL': '2725',
+    'BUNDARBO': '2726',
+    'JUGIONG': '2726',
+    'ADJUNGBILLY': '2727',
+    'COOLAC': '2727',
+    'GOBARRALONG': '2727',
+    'MINGAY': '2727',
+    'ADELONG': '2729',
+    'BANGADANG': '2729',
+    'CALIFAT': '2729',
+    'COOLEYS CREEK': '2729',
+    'DARLOW': '2729',
+    'ELLERSLIE': '2729',
+    'GRAHAMSTOWN': '2729',
+    'MOUNT ADRAH': '2729',
+    'MOUNT HOREB': '2729',
+    'MUNDARLO': '2729',
+    'SANDY GULLY': '2729',
+    'SHARPS CREEK': '2729',
+    'TUMBLONG': '2729',
+    'WESTWOOD': '2729',
+    'WONDALGA': '2729',
+    'YAVEN CREEK': '2729',
+    'BATLOW': '2730',
+    'KUNAMA': '2730',
+    'LOWER BAGO': '2730',
+    'BUNNALOO': '2731',
+    'MOAMA': '2731',
+    'TANTONAN': '2731',
+    'THYRA': '2731',
+    'WOMBOOTA': '2731',
+    'BARHAM': '2732',
+    'BURRABOI': '2732',
+    'COBRAMUNGA': '2732',
+    'GONN': '2732',
+    'NOORONG': '2732',
+    'THULE': '2732',
+    'TULLAKOOL': '2732',
+    'DHURAGOON': '2733',
+    'MOULAMEIN': '2733',
+    'NIEMUR': '2733',
+    'CUNNINYEUK': '2734',
+    'DILPURRA': '2734',
+    'KYALITE': '2734',
+    'MELLOOL': '2734',
+    'MOOLPA': '2734',
+    'STONY CROSSING': '2734',
+    'TOORANIE': '2734',
+    'WETUPPA': '2734',
+    'KORALEIGH': '2735',
+    'SPEEWA': '2735',
+    'GOODNIGHT': '2736',
+    'TOOLEYBUC': '2736',
+    'BENANEE': '2737',
+    'EUSTON': '2737',
+    'GOL GOL': '2738',
+    'MONAK': '2738',
+    'TRENTHAM CLIFFS': '2738',
+    'BURONGA': '2739',
+    'GLENMORE PARK': '2745',
+    'LUDDENHAM': '2745',
+    'MULGOA': '2745',
+    'REGENTVILLE': '2745',
+    'WALLACIA': '2745',
+    'CAMBRIDGE GARDENS': '2747',
+    'CAMBRIDGE PARK': '2747',
+    'CLAREMONT MEADOWS': '2747',
+    'LLANDILO': '2747',
+    'SHANES PARK': '2747',
+    'WERRINGTON': '2747',
+    'WERRINGTON COUNTY': '2747',
+    'WERRINGTON DOWNS': '2747',
+    'ORCHARD HILLS': '2748',
+    'CASTLEREAGH': '2749',
+    'CRANEBROOK': '2749',
+    'EMU HEIGHTS': '2750',
+    'EMU PLAINS': '2750',
+    'JAMISONTOWN': '2750',
+    'LEONAY': '2750',
+    'PENRITH': '2750',
+    'PENRITH PLAZA': '2750',
+    'PENRITH SOUTH': '2750',
+    'SOUTH PENRITH': '2750',
+    'SILVERDALE': '2752',
+    'WARRAGAMBA': '2752',
+    'AGNES BANKS': '2753',
+    'BOWEN MOUNTAIN': '2753',
+    'GROSE VALE': '2753',
+    'GROSE WOLD': '2753',
+    'HOBARTVILLE': '2753',
+    'LONDONDERRY': '2753',
+    'RICHMOND': '2753',
+    'RICHMOND LOWLANDS': '2753',
+    'YARRAMUNDI': '2753',
+    'NORTH RICHMOND': '2754',
+    'TENNYSON': '2754',
+    'THE SLOPES': '2754',
+    'BLIGH PARK': '2756',
+    'CATTAI': '2756',
+    'CENTRAL COLO': '2756',
+    'CLARENDON': '2756',
+    'COLO': '2756',
+    'COLO HEIGHTS': '2756',
+    'CORNWALLIS': '2756',
+    'CUMBERLAND REACH': '2756',
+    'EBENEZER': '2756',
+    'FREEMANS REACH': '2756',
+    'GLOSSODIA': '2756',
+    'LOWER PORTLAND': '2756',
+    'MAROOTA': '2756',
+    'MCGRATHS HILL': '2756',
+    'MELLONG': '2756',
+    'MULGRAVE': '2756',
+    'PITT TOWN': '2756',
+    'PITT TOWN BOTTOMS': '2756',
+    'SACKVILLE': '2756',
+    'SACKVILLE NORTH': '2756',
+    'SCHEYVILLE': '2756',
+    'SOUTH MAROOTA': '2756',
+    'SOUTH WINDSOR': '2756',
+    'UPPER COLO': '2756',
+    'WILBERFORCE': '2756',
+    'WINDSOR': '2756',
+    'WINDSOR DOWNS': '2756',
+    'WOMERAH': '2756',
+    'KURMOND': '2757',
+    'BERAMBING': '2758',
+    'BILPIN': '2758',
+    'BLAXLANDS RIDGE': '2758',
+    'EAST KURRAJONG': '2758',
+    'KURRAJONG': '2758',
+    'KURRAJONG HEIGHTS': '2758',
+    'KURRAJONG HILLS': '2758',
+    'MOUNT TOMAH': '2758',
+    'MOUNTAIN LAGOON': '2758',
+    'THE DEVILS WILDERNESS': '2758',
+    'WHEENY CREEK': '2758',
+    'ERSKINE PARK': '2759',
+    'COLYTON': '2760',
+    'NORTH ST MARYS': '2760',
+    'OXLEY PARK': '2760',
+    'ROPES CROSSING': '2760',
+    'ST MARYS': '2760',
+    'ST MARYS EAST': '2760',
+    'ST MARYS SOUTH': '2760',
+    'COLEBEE': '2761',
+    'DEAN PARK': '2761',
+    'GLENDENNING': '2761',
+    'GLENDENNING DC': '2761',
+    'HASSALL GROVE': '2761',
+    'OAKHURST': '2761',
+    'PLUMPTON': '2761',
+    'SCHOFIELDS': '2762',
+    'ACACIA GARDENS': '2763',
+    'QUAKERS HILL': '2763',
+    'BERKSHIRE PARK': '2765',
+    'BOX HILL': '2765',
+    'MARAYLYA': '2765',
+    'MARSDEN PARK': '2765',
+    'OAKVILLE': '2765',
+    'RIVERSTONE': '2765',
+    'VINEYARD': '2765',
+    'EASTERN CREEK': '2766',
+    'ROOTY HILL': '2766',
+    'DOONSIDE': '2767',
+    'WOODCROFT': '2767',
+    'GLENWOOD': '2768',
+    'PARKLEA': '2768',
+    'STANHOPE GARDENS': '2768',
+    'THE PONDS': '2769',
+    'BIDWILL': '2770',
+    'BLACKETT': '2770',
+    'DHARRUK': '2770',
+    'EMERTON': '2770',
+    'HEBERSHAM': '2770',
+    'LETHBRIDGE PARK': '2770',
+    'MINCHINBURY': '2770',
+    'MOUNT DRUITT': '2770',
+    'MOUNT DRUITT VILLAGE': '2770',
+    'SHALVEY': '2770',
+    'TREGEAR': '2770',
+    'WHALAN': '2770',
+    'WILLMOT': '2770',
+    'GLENBROOK': '2773',
+    'LAPSTONE': '2773',
+    'BLAXLAND': '2774',
+    'BLAXLAND EAST': '2774',
+    'MOUNT RIVERVIEW': '2774',
+    'WARRIMOO': '2774',
+    'CENTRAL MACDONALD': '2775',
+    'FERNANCES': '2775',
+    'GUNDERMAN': '2775',
+    'HIGHER MACDONALD': '2775',
+    'LAUGHTONDALE': '2775',
+    'LEETS VALE': '2775',
+    'LOWER MACDONALD': '2775',
+    'MARLOW': '2775',
+    'MOGO CREEK': '2775',
+    'PERRYS CROSSING': '2775',
+    'SINGLETONS MILL': '2775',
+    'SPENCER': '2775',
+    'ST ALBANS': '2775',
+    'UPPER MACDONALD': '2775',
+    'WEBBS CREEK': '2775',
+    'WISEMANS FERRY': '2775',
+    'WRIGHTS CREEK': '2775',
+    'FAULCONBRIDGE': '2776',
+    'HAWKESBURY HEIGHTS': '2777',
+    'SPRINGWOOD': '2777',
+    'SUN VALLEY': '2777',
+    'VALLEY HEIGHTS': '2777',
+    'WINMALEE': '2777',
+    'BULLS CAMP': '2778',
+    'LINDEN': '2778',
+    'WOODFORD': '2778',
+    'HAZELBROOK': '2779',
+    'KATOOMBA': '2780',
+    'KATOOMBA DC': '2780',
+    'LEURA': '2780',
+    'MEDLOW BATH': '2780',
+    'YOSEMITE': '2780',
+    'WENTWORTH FALLS': '2782',
+    'BULLABURRA': '2784',
+    'BLACKHEATH': '2785',
+    'MEGALONG': '2785',
+    'BELL': '2786',
+    'DARGAN': '2786',
+    'MOUNT IRVINE': '2786',
+    'MOUNT VICTORIA': '2786',
+    'MOUNT WILSON': '2786',
+    'BLACK SPRINGS': '2787',
+    'CHATHAM VALLEY': '2787',
+    'DUCKMALOI': '2787',
+    'GINGKIN': '2787',
+    'GURNANG': '2787',
+    'HAZELGROVE': '2787',
+    'JAUNTER': '2787',
+    'KANANGRA': '2787',
+    'MOUNT WERONG': '2787',
+    'MOZART': '2787',
+    'NORWAY': '2787',
+    'OBERON': '2787',
+    'PORTERS RETREAT': '2787',
+    'SHOOTERS HILL': '2787',
+    'TARANA': '2787',
+    'THE MEADOWS': '2787',
+    'BEN BULLEN': '2790',
+    'BLACKMANS FLAT': '2790',
+    'BOWENFELS': '2790',
+    'CLARENCE': '2790',
+    'COBAR PARK': '2790',
+    'CORNEY TOWN': '2790',
+    'CULLEN BULLEN': '2790',
+    'DOCTORS GAP': '2790',
+    'GANBENANG': '2790',
+    'HAMPTON': '2790',
+    'HARTLEY': '2790',
+    'HARTLEY VALE': '2790',
+    'HASSANS WALLS': '2790',
+    'HERMITAGE FLAT': '2790',
+    'JENOLAN': '2790',
+    'KANIMBLA': '2790',
+    'LIDSDALE': '2790',
+    'LITHGOW': '2790',
+    'LITHGOW DC': '2790',
+    'LITTLE HARTLEY': '2790',
+    'LITTLETON': '2790',
+    'LOWTHER': '2790',
+    'MARRANGAROO': '2790',
+    'MCKELLARS PARK': '2790',
+    'MORTS ESTATE': '2790',
+    'MOUNT LAMBIE': '2790',
+    'NEWNES': '2790',
+    'NEWNES PLATEAU': '2790',
+    'OAKY PARK': '2790',
+    'POTTERY ESTATE': '2790',
+    'RYDAL': '2790',
+    'SHEEDYS GULLY': '2790',
+    'SODWALLS': '2790',
+    'SOUTH BOWENFELS': '2790',
+    'SOUTH LITTLETON': '2790',
+    'STATE MINE GULLY': '2790',
+    'VALE OF CLWYDD': '2790',
+    'WOLGAN VALLEY': '2790',
+    'WOLLANGAMBE': '2790',
+    'CARCOAR': '2791',
+    'ERROWANBANG': '2791',
+    'BURNT YARDS': '2792',
+    'MANDURAMA': '2792',
+    'DARBYS FALLS': '2793',
+    'MOUNT MCDONALD': '2793',
+    'ROSEBERG': '2793',
+    'BUMBALDRY': '2794',
+    'COWRA': '2794',
+    'HOVELLS CREEK': '2794',
+    'MOUNT COLLINS': '2794',
+    'NOONBINNA': '2794',
+    'WATTAMONDARA': '2794',
+    'ABERCROMBIE': '2795',
+    'ABERCROMBIE RIVER': '2795',
+    'ARKELL': '2795',
+    'ARKSTONE': '2795',
+    'BALD RIDGE': '2795',
+    'BALLYROE': '2795',
+    'BATHAMPTON': '2795',
+    'BATHURST': '2795',
+    'BILLYWILLINGA': '2795',
+    'BOX RIDGE': '2795',
+    'BREWONGLE': '2795',
+    'BRUINBUN': '2795',
+    'BURRAGA': '2795',
+    'CALOOLA': '2795',
+    'CHARLES STURT UNIVERSITY': '2795',
+    'CHARLTON': '2795',
+    'CLEAR CREEK': '2795',
+    'COPPERHANNIA': '2795',
+    'COW FLAT': '2795',
+    'CRUDINE': '2795',
+    'CURRAGH': '2795',
+    'DARK CORNER': '2795',
+    'DOG ROCKS': '2795',
+    'DUNKELD': '2795',
+    'DURAMANA': '2795',
+    'EGLINTON': '2795',
+    'ESSINGTON': '2795',
+    'EVANS PLAINS': '2795',
+    'FITZGERALDS VALLEY': '2795',
+    'FOREST GROVE': '2795',
+    'FOSTERS VALLEY': '2795',
+    'FREEMANTLE': '2795',
+    'GEMALLA': '2795',
+    'GEORGES PLAINS': '2795',
+    'GILMANDYKE': '2795',
+    'GLANMIRE': '2795',
+    'GORMANS HILL': '2795',
+    'GOWAN': '2795',
+    'HOBBYS YARDS': '2795',
+    'ISABELLA': '2795',
+    'JEREMY': '2795',
+    'JUDDS CREEK': '2795',
+    'KELSO': '2795',
+    'KILLONGBUTTA': '2795',
+    'KIRKCONNELL': '2795',
+    'LAFFING WATERS': '2795',
+    'LIMEKILNS': '2795',
+    'LLANARTH': '2795',
+    'LOCKSLEY': '2795',
+    'MEADOW FLAT': '2795',
+    'MILKERS FLAT': '2795',
+    'MILLAH MURRAH': '2795',
+    'MOORILDA': '2795',
+    'MOUNT DAVID': '2795',
+    'MOUNT PANORAMA': '2795',
+    'MOUNT RANKIN': '2795',
+    'NAPOLEON REEF': '2795',
+    'NEWBRIDGE': '2795',
+    "O'CONNELL": '2795',
+    'ORTON PARK': '2795',
+    'PALING YARDS': '2795',
+    'PALMERS OAKY': '2795',
+    'PEEL': '2795',
+    'PERTHVILLE': '2795',
+    'RAGLAN': '2795',
+    'ROBIN HILL': '2795',
+    'ROCK FOREST': '2795',
+    'ROCKLEY': '2795',
+    'ROCKLEY MOUNT': '2795',
+    'SOFALA': '2795',
+    'SOUTH BATHURST': '2795',
+    'STEWARTS MOUNT': '2795',
+    'SUNNY CORNER': '2795',
+    'TAMBAROORA': '2795',
+    'TANNAS MOUNT': '2795',
+    'THE LAGOON': '2795',
+    'TRIANGLE FLAT': '2795',
+    'TRUNKEY CREEK': '2795',
+    'TURONDALE': '2795',
+    'TWENTY FORESTS': '2795',
+    'UPPER TURON': '2795',
+    'WALANG': '2795',
+    'WAMBOOL': '2795',
+    'WATTLE FLAT': '2795',
+    'WATTON': '2795',
+    'WEST BATHURST': '2795',
+    'WHITE ROCK': '2795',
+    'WIAGDON': '2795',
+    'WIMBLEDON': '2795',
+    'WINBURNDALE': '2795',
+    'WINDRADYNE': '2795',
+    'WISEMANS CREEK': '2795',
+    'YETHOLME': '2795',
+    'GARLAND': '2797',
+    'BYNG': '2798',
+    'FLYERS CREEK': '2798',
+    'FOREST REEFS': '2798',
+    'GUYONG': '2798',
+    'MILLTHORPE': '2798',
+    'SPRING TERRACE': '2798',
+    'TALLWOOD': '2798',
+    'BLAYNEY': '2799',
+    'BROWNS CREEK': '2799',
+    'FITZGERALDS MOUNT': '2799',
+    'NEVILLE': '2799',
+    'VITTORIA': '2799',
+    'BELGRAVIA': '2800',
+    'BLOOMFIELD': '2800',
+    'BORENORE': '2800',
+    'CADIA': '2800',
+    'CANOBOLAS': '2800',
+    'CARGO': '2800',
+    'CLERGATE': '2800',
+    'CLIFTON GROVE': '2800',
+    'EMU SWAMP': '2800',
+    'KALEENTHA': '2800',
+    'KANGAROOBIE': '2800',
+    'KERRS CREEK': '2800',
+    'LEWIS PONDS': '2800',
+    'LIDSTER': '2800',
+    'LOWER LEWIS PONDS': '2800',
+    'LUCKNOW': '2800',
+    'MARCH': '2800',
+    'MULLION CREEK': '2800',
+    'NANGAR': '2800',
+    'NASHDALE': '2800',
+    'OPHIR': '2800',
+    'ORANGE': '2800',
+    'ORANGE DC': '2800',
+    'ORANGE EAST': '2800',
+    'PANUARA': '2800',
+    'PINNACLE': '2800',
+    'SHADFORTH': '2800',
+    'SPRING CREEK': '2800',
+    'SPRINGSIDE': '2800',
+    'SUMMER HILL CREEK': '2800',
+    'TOWAC': '2800',
+    'WALDEGRAVE': '2800',
+    'WINDERA': '2800',
+    'BENDICK MURRELL': '2803',
+    'CROWTHER': '2803',
+    'WIRRIMAH': '2803',
+    'BILLIMARI': '2804',
+    'CANOWINDRA': '2804',
+    'MOORBEL': '2804',
+    'NYRANG CREEK': '2804',
+    'GOOLOOGONG': '2805',
+    'EUGOWRA': '2806',
+    'EULIMORE': '2806',
+    'KOORAWATHA': '2807',
+    'WYANGALA': '2808',
+    'GREENETHORPE': '2809',
+    'BIMBI': '2810',
+    'CARAGABAL': '2810',
+    'GLENELG': '2810',
+    'GRENFELL': '2810',
+    'PINEY RANGE': '2810',
+    'PULLABOOKA': '2810',
+    'WARRADERRY': '2810',
+    'APSLEY': '2820',
+    'ARTHURVILLE': '2820',
+    'BAKERS SWAMP': '2820',
+    'BODANGORA': '2820',
+    'COMOBELLA': '2820',
+    'CURRA CREEK': '2820',
+    'DRIPSTONE': '2820',
+    'FARNHAM': '2820',
+    'GOLLAN': '2820',
+    'LAKE BURRENDONG': '2820',
+    'MARYVALE': '2820',
+    'MONTEFIORES': '2820',
+    'MOOKERAWA': '2820',
+    'MOUNT AQUILA': '2820',
+    'MOUNT ARTHUR': '2820',
+    'MUMBIL': '2820',
+    'NEUREA': '2820',
+    'SPICERS CREEK': '2820',
+    'STUART TOWN': '2820',
+    'SUNTOP': '2820',
+    'WALMER': '2820',
+    'WELLINGTON': '2820',
+    'WUULUMAN': '2820',
+    'YARRAGAL': '2820',
+    'BURROWAY': '2821',
+    'NARROMINE': '2821',
+    'BUNDEMAR': '2823',
+    'CATHUNDRAL': '2823',
+    'DANDALOO': '2823',
+    'GIN GIN': '2823',
+    'TRANGIE': '2823',
+    'BEEMUNNEL': '2824',
+    'EENAWEENA': '2824',
+    'MARTHAGUY': '2824',
+    'MOUNT FOSTER': '2824',
+    'MOUNT HARRIS': '2824',
+    'MUMBLEBONE PLAIN': '2824',
+    'PIGEONBAH': '2824',
+    'RAVENSWOOD': '2824',
+    'SNAKES PLAIN': '2824',
+    'TENANDRA': '2824',
+    'WARREN': '2824',
+    'BABINDA': '2825',
+    'BOBADAH': '2825',
+    'BOGAN': '2825',
+    'BUDDABADAH': '2825',
+    'CANONBA': '2825',
+    'FIVE WAYS': '2825',
+    'HONEYBUGLE': '2825',
+    'MIANDETTA': '2825',
+    'MULLA': '2825',
+    'MULLENGUDGERY': '2825',
+    'MURRAWOMBIE': '2825',
+    'NYNGAN': '2825',
+    'PANGEE': '2825',
+    'BEARBONG': '2827',
+    'BIDDON': '2827',
+    'BREELONG': '2827',
+    'COLLIE': '2827',
+    'CURBAN': '2827',
+    'GILGANDRA': '2827',
+    'MERRIGAL': '2827',
+    'BLACK HOLLOW': '2828',
+    'BOURBAH': '2828',
+    'GULARGAMBONE': '2828',
+    'MOUNT TENANDRA': '2828',
+    'QUANDA': '2828',
+    'TONDERBURINE': '2828',
+    'WARRUMBUNGLE': '2828',
+    'COMBARA': '2829',
+    'CONIMBIA': '2829',
+    'COONAMBLE': '2829',
+    'GILGOOMA': '2829',
+    'MAGOMETON': '2829',
+    'NEBEA': '2829',
+    'PINE GROVE': '2829',
+    'TERIDGERIE': '2829',
+    'URAWILKIE': '2829',
+    'WINGADEE': '2829',
+    'BALLIMORE': '2830',
+    'BARBIGAL': '2830',
+    'BENI': '2830',
+    'BENOLONG': '2830',
+    'BOOTHENBA': '2830',
+    'BROCKLEHURST': '2830',
+    'BRUAH': '2830',
+    'BUNGLEGUMBIE': '2830',
+    'BURRABADINE': '2830',
+    'BUTLERS FALLS': '2830',
+    'COOLBAGGIE': '2830',
+    'CUMBOOGLE': '2830',
+    'DELROY GARDENS': '2830',
+    'DICKYGUNDI': '2830',
+    'DUBBO': '2830',
+    'DUBBO DC': '2830',
+    'DUBBO EAST': '2830',
+    'DUBBO GROVE': '2830',
+    'DUBBO WEST': '2830',
+    'DUNGARY': '2830',
+    'ERSKINE': '2830',
+    'ESCHOL': '2830',
+    'EULOMOGO': '2830',
+    'HYANDRA': '2830',
+    'KICKABIL': '2830',
+    'MANERA HEIGHTS': '2830',
+    'MINORE': '2830',
+    'MOGRIGUY': '2830',
+    'MURRUMBIDGERIE': '2830',
+    'NORTH DUBBO': '2830',
+    'ORANA HEIGHTS': '2830',
+    'RAWSONVILLE': '2830',
+    'SOUTH DUBBO': '2830',
+    'TALBRAGAR': '2830',
+    'TERRAMUNGAMINE': '2830',
+    'THE SPRINGS': '2830',
+    'TOONGI': '2830',
+    'TROY JUNCTION': '2830',
+    'WARRIE': '2830',
+    'WEST DUBBO': '2830',
+    'WHYLANDRA CROSSING': '2830',
+    'ARMATREE': '2831',
+    'BALLADORAN': '2831',
+    'BILLEROY': '2831',
+    'BRENDA': '2831',
+    'BULLAGREEN': '2831',
+    'BYROCK': '2831',
+    'CARINDA': '2831',
+    'COOLABAH': '2831',
+    'ELONG ELONG': '2831',
+    'EUMUNGERIE': '2831',
+    'GEURIE': '2831',
+    'GIRILAMBONE': '2831',
+    'GOODOOGA': '2831',
+    'GUNGALMAN': '2831',
+    'HERMIDALE': '2831',
+    'MACQUARIE MARSHES': '2831',
+    'MERRYGOEN': '2831',
+    'MURIEL': '2831',
+    'NEILREX': '2831',
+    'NEVERTIRE': '2831',
+    'NUBINGERIE': '2831',
+    'NYMAGEE': '2831',
+    'PINE CLUMP': '2831',
+    'PONTO': '2831',
+    'QUAMBONE': '2831',
+    'TERRABELLA': '2831',
+    'THE MARRA': '2831',
+    'TOOLOON': '2831',
+    'TOORAWEENAH': '2831',
+    'WESTELLA': '2831',
+    'WONGARBON': '2831',
+    'ANGLEDOOL': '2832',
+    'COME BY CHANCE': '2832',
+    'CRYON': '2832',
+    'CUMBORAH': '2832',
+    'WALGETT': '2832',
+    'COLLARENEBRI': '2833',
+    'LIGHTNING RIDGE': '2834',
+    'BULLA': '2835',
+    'CANBELEGO': '2835',
+    'COBAR': '2835',
+    'CUBBA': '2835',
+    'GILGUNNIA': '2835',
+    'IRYMPLE': '2835',
+    'KERRIGUNDI': '2835',
+    'KULWIN': '2835',
+    'NOONA': '2835',
+    'TINDAREY': '2835',
+    'GEMVILLE': '2836',
+    'WHITE CLIFFS': '2836',
+    'WILCANNIA': '2836',
+    'BREWARRINA': '2839',
+    'GONGOLGON': '2839',
+    'TALAWANTA': '2839',
+    'WEILMORINGLE': '2839',
+    'BARRINGUN': '2840',
+    'BOURKE': '2840',
+    'ENNGONIA': '2840',
+    'FORDS BRIDGE': '2840',
+    'GUMBALIE': '2840',
+    'GUNDERBOOKA': '2840',
+    'HUNGERFORD': '2840',
+    'LOUTH': '2840',
+    'TILPA': '2840',
+    'URISINO': '2840',
+    'WANAARING': '2840',
+    'YANTABULLA': '2840',
+    'MENDOORAN': '2842',
+    'MOLLYAN': '2842',
+    'WATTLE SPRINGS': '2842',
+    'YARRAGRIN': '2842',
+    'COOLAH': '2843',
+    'BIRRIWA': '2844',
+    'DUNEDOO': '2844',
+    'LEADVILLE': '2844',
+    'WALLERAWANG': '2845',
+    'CAPERTEE': '2846',
+    'GLEN DAVIS': '2846',
+    'KANGAROO FLAT': '2846',
+    'ROUND SWAMP': '2846',
+    'PORTLAND': '2847',
+    'BROGANS CREEK': '2848',
+    'CHARBON': '2848',
+    'CLANDULLA': '2848',
+    'KANDOS': '2848',
+    'BOGEE': '2849',
+    'BREAKFAST CREEK': '2849',
+    'BUDDEN': '2849',
+    'BYLONG': '2849',
+    'CAMBOON': '2849',
+    'CARWELL': '2849',
+    'COGGAN': '2849',
+    'COXS CREEK': '2849',
+    'COXS CROWN': '2849',
+    'DABEE': '2849',
+    'DUNGEREE': '2849',
+    'DUNVILLE LOOP': '2849',
+    'GINGHI': '2849',
+    'GLEN ALICE': '2849',
+    'GROWEE': '2849',
+    'KELGOOLA': '2849',
+    'LEE CREEK': '2849',
+    'MOUNT MARSDEN': '2849',
+    'MURRUMBO': '2849',
+    'NULLO MOUNTAIN': '2849',
+    'OLINDA': '2849',
+    'PINNACLE SWAMP': '2849',
+    'PYANGLE': '2849',
+    'RYLSTONE': '2849',
+    'UPPER BYLONG': '2849',
+    'UPPER GROWEE': '2849',
+    'UPPER NILE': '2849',
+    'WIRRABA': '2849',
+    'AARONS PASS': '2850',
+    'APPLE TREE FLAT': '2850',
+    'AVISFORD': '2850',
+    'BARA': '2850',
+    'BARIGAN': '2850',
+    'BEN BUCKLEY': '2850',
+    'BOCOBLE': '2850',
+    'BOMBIRA': '2850',
+    'BOTOBOLAR': '2850',
+    'BUCKAROO': '2850',
+    'BUDGEE BUDGEE': '2850',
+    'BURRUNDULLA': '2850',
+    'CAERLEON': '2850',
+    'CANADIAN LEAD': '2850',
+    'CARCALGONG': '2850',
+    'COLLINGWOOD': '2850',
+    'COOKS GAP': '2850',
+    'COOYAL': '2850',
+    'CROSS ROADS': '2850',
+    'CUDGEGONG': '2850',
+    'CULLENBONE': '2850',
+    'CUMBO': '2850',
+    'ERUDGERE': '2850',
+    'EURUNDEREE': '2850',
+    'FROG ROCK': '2850',
+    'GALAMBINE': '2850',
+    'GLEN AYR': '2850',
+    'GRATTAI': '2850',
+    'GREEN GULLY': '2850',
+    'HARGRAVES': '2850',
+    'HAVILAH': '2850',
+    'HAYES GAP': '2850',
+    'HILL END': '2850',
+    'HOME RULE': '2850',
+    'ILFORD': '2850',
+    'KAINS FLAT': '2850',
+    'LINBURN': '2850',
+    'LUE': '2850',
+    'MAITLAND BAR': '2850',
+    'MENAH': '2850',
+    'MEROO': '2850',
+    'MONIVAE': '2850',
+    'MOOLARBEN': '2850',
+    'MOUNT FROME': '2850',
+    'MOUNT KNOWLES': '2850',
+    'MUDGEE': '2850',
+    'MULLAMUDDY': '2850',
+    'MUNGHORN': '2850',
+    'PIAMBONG': '2850',
+    'PUTTA BUCCA': '2850',
+    'PYRAMUL': '2850',
+    'QUEENS PINCH': '2850',
+    'RIVERLEA': '2850',
+    'RUNNING STREAM': '2850',
+    'SALLYS FLAT': '2850',
+    'SPRING FLAT': '2850',
+    'ST FILLANS': '2850',
+    'TICHULAR': '2850',
+    'TOTNES VALLEY': '2850',
+    'TRIAMBLE': '2850',
+    'TURILL': '2850',
+    'TWELVE MILE': '2850',
+    'ULAN': '2850',
+    'ULLAMALLA': '2850',
+    'WILBETREE': '2850',
+    'WILPINJONG': '2850',
+    'WINDEYER': '2850',
+    'WOLLAR': '2850',
+    'WORLDS END': '2850',
+    'YARRABIN': '2850',
+    'BARNEYS REEF': '2852',
+    'BERYL': '2852',
+    'BIRAGANBIL': '2852',
+    'BUNGABA': '2852',
+    'COPE': '2852',
+    'CUMBANDRY': '2852',
+    'GOOLMA': '2852',
+    'GULGONG': '2852',
+    'MEBUL': '2852',
+    'MEROTHERIE': '2852',
+    'STUBBO': '2852',
+    'TALLAWANG': '2852',
+    'BOWAN PARK': '2864',
+    'CUDAL': '2864',
+    'MURGA': '2864',
+    'TOOGONG': '2864',
+    'BOCOBRA': '2865',
+    'GREGRA': '2865',
+    'GUMBLE': '2865',
+    'MANILDRA': '2865',
+    'AMAROO': '2866',
+    'BOOMEY': '2866',
+    'COPPER HILL': '2866',
+    'CUNDUMBUL': '2866',
+    'EUCHAREENA': '2866',
+    'GARRA': '2866',
+    'LARRAS LEE': '2866',
+    'MOLONG': '2866',
+    'BALDRY': '2867',
+    'CUMNOCK': '2867',
+    'EURIMBLA': '2867',
+    'LOOMBAH': '2867',
+    'YULLUNDRY': '2867',
+    'BOURNEWOOD': '2868',
+    'NORTH YEOVAL': '2868',
+    'OBLEY': '2868',
+    'YEOVAL': '2868',
+    'PEAK HILL': '2869',
+    'TOMINGLEY': '2869',
+    'TREWILGA': '2869',
+    'ALECTOWN': '2870',
+    'BEARGAMIL': '2870',
+    'BINDOGUNDRA': '2870',
+    'BROLGAN': '2870',
+    'BUMBERRY': '2870',
+    'COOKAMIDGERA': '2870',
+    'COOKS MYALLS': '2870',
+    'GOOBANG': '2870',
+    'GOONUMBLA': '2870',
+    'KADINA': '2870',
+    'MANDAGERY': '2870',
+    'MICKIBRI': '2870',
+    'MUGINCOBLE': '2870',
+    'NANARDINE': '2870',
+    'PARKESBOROUGH': '2870',
+    'SHALLOW RUSH': '2870',
+    'TICHBORNE': '2870',
+    'BANDON': '2871',
+    'BEDGEREBONG': '2871',
+    'BUNDABURRAH': '2871',
+    'CALARIE': '2871',
+    'CARRAWABBITY': '2871',
+    'CORINELLA': '2871',
+    'CUMBIJOWA': '2871',
+    'DAROOBALGIE': '2871',
+    'FAIRHOLME': '2871',
+    'FORBES': '2871',
+    'GAREMA': '2871',
+    'GRAWLIN': '2871',
+    'GUNNING GAP': '2871',
+    'JEMALONG': '2871',
+    'MULYANDRY': '2871',
+    'OOMA': '2871',
+    'WARROO': '2871',
+    'WEELONG': '2871',
+    'WIRRINYA': '2871',
+    'YARRAGONG': '2871',
+    'ALBERT': '2873',
+    'LANSDALE': '2873',
+    'MIAMLEY': '2873',
+    'MIAMLEY NORTH': '2873',
+    'TOTTENHAM': '2873',
+    'TULLAMORE': '2874',
+    'YETHERA': '2874',
+    'BRUIE PLAINS': '2875',
+    'FIFIELD': '2875',
+    'OOTHA': '2875',
+    'THE TROFFS': '2875',
+    'TRUNDLE': '2875',
+    'YARRABANDAI': '2875',
+    'BOGAN GATE': '2876',
+    'BOTFIELDS': '2876',
+    'GUNNINGBLAND': '2876',
+    'NELUNGALOO': '2876',
+    'BOONA MOUNT': '2877',
+    'CONDOBOLIN': '2877',
+    'DERRIWONG': '2877',
+    'EREMERANG': '2877',
+    'EUABALONG': '2877',
+    'EUABALONG WEST': '2877',
+    'KIACATOO': '2877',
+    'MILBY': '2877',
+    'MOUNT HOPE': '2877',
+    'MULGUTHRIE': '2877',
+    'BEILPAJAH': '2878',
+    'CONOBLE': '2878',
+    'IVANHOE': '2878',
+    'MANARA': '2878',
+    'MOSSGIEL': '2878',
+    'TRIDA': '2878',
+    'COPI HOLLOW': '2879',
+    'MENINDEE': '2879',
+    'BROKEN HILL': '2880',
+    'BROKEN HILL NORTH': '2880',
+    'BROKEN HILL WEST': '2880',
+    'BURNS': '2880',
+    'CAMERON CORNER': '2880',
+    'EURIOWIE': '2880',
+    'FOWLERS GAP': '2880',
+    'KINALUNG': '2880',
+    'LITTLE TOPAR': '2880',
+    'MILPARINKA': '2880',
+    'MOUNT GIPPS': '2880',
+    'PACKSADDLE': '2880',
+    'SILVERTON': '2880',
+    'SOUTH BROKEN HILL': '2880',
+    'STEPHENS CREEK': '2880',
+    'TIBOOBURRA': '2880',
+    'LORD HOWE ISLAND': '2898',
+    'NORFOLK ISLAND': '2899',
+    'GREENWAY': '2900',
+    'TUGGERANONG': '2900',
+    'KAMBAH': '2902',
+    'ERINDALE CENTRE': '2903',
+    'WANNIASSA': '2903',
+    'FADDEN': '2904',
+    'MACARTHUR': '2904',
+    'MONASH': '2904',
+    'BONYTHON': '2905',
+    'CALWELL': '2905',
+    'ISABELLA PLAINS': '2905',
+    'RICHARDSON': '2905',
+    'THEODORE': '2905',
+    'BANKS': '2906',
+    'CONDER': '2906',
+    'CRACE': '2911',
+    'GUNGAHLIN': '2912',
+    'FRANKLIN': '2913',
+    'GINNINDERRA VILLAGE': '2913',
+    'NGUNNAWAL': '2913',
+    'NICHOLLS': '2913',
+    'BONNER': '2914',
+    'FORDE': '2914',
+    'HARRISON': '2914',
+    'MELBOURNE': '3000',
+    'EAST MELBOURNE': '3002',
+    'WEST MELBOURNE': '3003',
+    'ST KILDA ROAD CENTRAL': '3004',
+    'WORLD TRADE CENTRE': '3005',
+    'SOUTHBANK': '3006',
+    'DOCKLANDS': '3008',
+    'UNIVERSITY OF MELBOURNE': '3010',
+    'FOOTSCRAY': '3011',
+    'SEDDON': '3011',
+    'SEDDON WEST': '3011',
+    'KINGSVILLE': '3012',
+    'KINGSVILLE WEST': '3012',
+    'MAIDSTONE': '3012',
+    'WEST FOOTSCRAY': '3012',
+    'YARRAVILLE': '3013',
+    'YARRAVILLE WEST': '3013',
+    'SOUTH KINGSVILLE': '3015',
+    'SPOTSWOOD': '3015',
+    'WILLIAMSTOWN': '3016',
+    'WILLIAMSTOWN NORTH': '3016',
+    'ALTONA': '3018',
+    'SEAHOLME': '3018',
+    'BRAYBROOK': '3019',
+    'BRAYBROOK NORTH': '3019',
+    'ROBINSON': '3019',
+    'ALBION': '3020',
+    'GLENGALA': '3020',
+    'SUNSHINE NORTH': '3020',
+    'SUNSHINE WEST': '3020',
+    'ALBANVALE': '3021',
+    'KEALBA': '3021',
+    'ARDEER': '3022',
+    'DEER PARK EAST': '3022',
+    'BURNSIDE': '3023',
+    'BURNSIDE HEIGHTS': '3023',
+    'CAIRNLEA': '3023',
+    'CAROLINE SPRINGS': '3023',
+    'DEER PARK': '3023',
+    'DEER PARK NORTH': '3023',
+    'RAVENHALL': '3023',
+    'MAMBOURIN': '3024',
+    'MOUNT COTTRELL': '3024',
+    'WYNDHAM VALE': '3024',
+    'ALTONA EAST': '3025',
+    'ALTONA GATE': '3025',
+    'ALTONA NORTH': '3025',
+    'LAVERTON NORTH': '3026',
+    'LAVERTON RAAF': '3027',
+    'WILLIAMS LANDING': '3027',
+    'WILLIAMS RAAF': '3027',
+    'ALTONA MEADOWS': '3028',
+    'LAVERTON': '3028',
+    'SEABROOK': '3028',
+    'HOPPERS CROSSING': '3029',
+    'TARNEIT': '3029',
+    'TRUGANINA': '3029',
+    'COCOROC': '3030',
+    'DERRIMUT': '3030',
+    'POINT COOK': '3030',
+    'QUANDONG': '3030',
+    'WERRIBEE': '3030',
+    'WERRIBEE SOUTH': '3030',
+    'FLEMINGTON': '3031',
+    'ASCOT VALE': '3032',
+    'HIGHPOINT CITY': '3032',
+    'MARIBYRNONG': '3032',
+    'TRAVANCORE': '3032',
+    'KEILOR EAST': '3033',
+    'AVONDALE HEIGHTS': '3034',
+    'KEILOR': '3036',
+    'KEILOR NORTH': '3036',
+    'DELAHEY': '3037',
+    'HILLSIDE': '3037',
+    'TAYLORS HILL': '3037',
+    'KEILOR DOWNS': '3038',
+    'KEILOR LODGE': '3038',
+    'TAYLORS LAKES': '3038',
+    'WATERGARDENS': '3038',
+    'MOONEE PONDS': '3039',
+    'ABERFELDIE': '3040',
+    'ESSENDON': '3040',
+    'ESSENDON WEST': '3040',
+    'ESSENDON NORTH': '3041',
+    'STRATHMORE': '3041',
+    'STRATHMORE HEIGHTS': '3041',
+    'AIRPORT WEST': '3042',
+    'KEILOR PARK': '3042',
+    'NIDDRIE': '3042',
+    'GLADSTONE PARK': '3043',
+    'GOWANBRAE': '3043',
+    'TULLAMARINE': '3043',
+    'PASCOE VALE': '3044',
+    'PASCOE VALE SOUTH': '3044',
+    'MELBOURNE AIRPORT': '3045',
+    'HADFIELD': '3046',
+    'OAK PARK': '3046',
+    'BROADMEADOWS': '3047',
+    'DALLAS': '3047',
+    'JACANA': '3047',
+    'COOLAROO': '3048',
+    'MEADOW HEIGHTS': '3048',
+    'ATTWOOD': '3049',
+    'CALDER PARK': '3049',
+    'WESTMEADOWS': '3049',
+    'ROYAL MELBOURNE HOSPITAL': '3050',
+    'HOTHAM HILL': '3051',
+    'NORTH MELBOURNE': '3051',
+    'MELBOURNE UNIVERSITY': '3052',
+    'CARLTON SOUTH': '3053',
+    'CARLTON NORTH': '3054',
+    'PRINCES HILL': '3054',
+    'BRUNSWICK SOUTH': '3055',
+    'BRUNSWICK WEST': '3055',
+    'MOONEE VALE': '3055',
+    'MORELAND WEST': '3055',
+    'BRUNSWICK': '3056',
+    'BRUNSWICK LOWER': '3056',
+    'BRUNSWICK NORTH': '3056',
+    'BRUNSWICK EAST': '3057',
+    'LYGON STREET NORTH': '3057',
+    'BATMAN': '3058',
+    'COBURG': '3058',
+    'COBURG NORTH': '3058',
+    'MERLYNSTON': '3058',
+    'MORELAND': '3058',
+    'GREENVALE': '3059',
+    'FAWKNER': '3060',
+    'FAWKNER EAST': '3060',
+    'FAWKNER NORTH': '3060',
+    'CAMPBELLFIELD': '3061',
+    'OAKLANDS JUNCTION': '3063',
+    'YUROKE': '3063',
+    'CRAIGIEBURN': '3064',
+    'DONNYBROOK': '3064',
+    'KALKALLO': '3064',
+    'MICKLEHAM': '3064',
+    'ROXBURGH PARK': '3064',
+    'FITZROY': '3065',
+    'COLLINGWOOD NORTH': '3066',
+    'CLIFTON HILL': '3068',
+    'FITZROY NORTH': '3068',
+    'NORTHCOTE': '3070',
+    'NORTHCOTE SOUTH': '3070',
+    'THORNBURY': '3071',
+    'GILBERTON': '3072',
+    'NORTHLAND CENTRE': '3072',
+    'PRESTON': '3072',
+    'PRESTON LOWER': '3072',
+    'PRESTON SOUTH': '3072',
+    'PRESTON WEST': '3072',
+    'REGENT WEST': '3072',
+    'KEON PARK': '3073',
+    'RESERVOIR': '3073',
+    'RESERVOIR EAST': '3073',
+    'RESERVOIR NORTH': '3073',
+    'RESERVOIR SOUTH': '3073',
+    'THOMASTOWN': '3074',
+    'LALOR': '3075',
+    'LALOR PLAZA': '3075',
+    'EPPING DC': '3076',
+    'ALPHINGTON': '3078',
+    'IVANHOE EAST': '3079',
+    'IVANHOE NORTH': '3079',
+    'BELLFIELD': '3081',
+    'HEIDELBERG HEIGHTS': '3081',
+    'HEIDELBERG RGH': '3081',
+    'HEIDELBERG WEST': '3081',
+    'MILL PARK': '3082',
+    'BUNDOORA': '3083',
+    'KINGSBURY': '3083',
+    'LA TROBE UNIVERSITY': '3083',
+    'BANYULE': '3084',
+    'EAGLEMONT': '3084',
+    'HEIDELBERG': '3084',
+    'ROSANNA': '3084',
+    'VIEWBANK': '3084',
+    'MACLEOD': '3085',
+    'MACLEOD WEST': '3085',
+    'YALLAMBIE': '3085',
+    'WATSONIA': '3087',
+    'WATSONIA NORTH': '3087',
+    'BRIAR HILL': '3088',
+    'GREENSBOROUGH': '3088',
+    'SAINT HELENA': '3088',
+    'DIAMOND CREEK': '3089',
+    'PLENTY': '3090',
+    'YARRAMBAT': '3091',
+    'LOWER PLENTY': '3093',
+    'MONTMORENCY': '3094',
+    'ELTHAM NORTH': '3095',
+    'RESEARCH': '3095',
+    'WATTLE GLEN': '3096',
+    'BEND OF ISLANDS': '3097',
+    'KANGAROO GROUND': '3097',
+    'ARTHURS CREEK': '3099',
+    'COTTLES BRIDGE': '3099',
+    'HURSTBRIDGE': '3099',
+    'NUTFIELD': '3099',
+    'STRATHEWEN': '3099',
+    'COTHAM': '3101',
+    'KEW EAST': '3102',
+    'BALWYN': '3103',
+    'BALWYN EAST': '3103',
+    'BALWYN NORTH': '3104',
+    'GREYTHORN': '3104',
+    'BULLEEN': '3105',
+    'BULLEEN SOUTH': '3105',
+    'TEMPLESTOWE': '3106',
+    'TEMPLESTOWE LOWER': '3107',
+    'DONCASTER': '3108',
+    'DONCASTER EAST': '3109',
+    'DONCASTER HEIGHTS': '3109',
+    'THE PINES': '3109',
+    'TUNSTALL SQUARE PO': '3109',
+    'DONVALE': '3111',
+    'NORTH WARRANDYTE': '3113',
+    'WARRANDYTE': '3113',
+    'PARK ORCHARDS': '3114',
+    'WONGA PARK': '3115',
+    'CHIRNSIDE PARK': '3116',
+    'BURNLEY': '3121',
+    'BURNLEY NORTH': '3121',
+    'RICHMOND EAST': '3121',
+    'RICHMOND NORTH': '3121',
+    'RICHMOND SOUTH': '3121',
+    'AUBURN SOUTH': '3122',
+    'GLENFERRIE SOUTH': '3122',
+    'HAWTHORN': '3122',
+    'HAWTHORN NORTH': '3122',
+    'HAWTHORN WEST': '3122',
+    'HAWTHORN EAST': '3123',
+    'CAMBERWELL NORTH': '3124',
+    'CAMBERWELL SOUTH': '3124',
+    'CAMBERWELL WEST': '3124',
+    'HARTWELL': '3124',
+    'MIDDLE CAMBERWELL': '3124',
+    'BENNETTSWOOD': '3125',
+    'SURREY HILLS SOUTH': '3125',
+    'CAMBERWELL EAST': '3126',
+    'MONT ALBERT': '3127',
+    'SURREY HILLS': '3127',
+    'SURREY HILLS NORTH': '3127',
+    'BOX HILL CENTRAL': '3128',
+    'BOX HILL SOUTH': '3128',
+    'HOUSTON': '3128',
+    'WATTLE PARK': '3128',
+    'BOX HILL NORTH': '3129',
+    'KERRIMUIR': '3129',
+    'MONT ALBERT NORTH': '3129',
+    'BLACKBURN': '3130',
+    'BLACKBURN NORTH': '3130',
+    'BLACKBURN SOUTH': '3130',
+    'LABURNUM': '3130',
+    'BRENTFORD SQUARE': '3131',
+    'NUNAWADING': '3131',
+    'MITCHAM': '3132',
+    'MITCHAM NORTH': '3132',
+    'RANGEVIEW': '3132',
+    'VERMONT': '3133',
+    'VERMONT SOUTH': '3133',
+    'RINGWOOD NORTH': '3134',
+    'WARRANDYTE SOUTH': '3134',
+    'WARRANWOOD': '3134',
+    'BEDFORD ROAD': '3135',
+    'HEATHMONT': '3135',
+    'RINGWOOD EAST': '3135',
+    'CROYDON HILLS': '3136',
+    'CROYDON NORTH': '3136',
+    'CROYDON SOUTH': '3136',
+    'KILSYTH': '3137',
+    'KILSYTH SOUTH': '3137',
+    'MOOROOLBARK': '3138',
+    'BEENAK': '3139',
+    'DON VALLEY': '3139',
+    'HODDLES CREEK': '3139',
+    'LAUNCHING PLACE': '3139',
+    'SEVILLE': '3139',
+    'SEVILLE EAST': '3139',
+    'WANDIN EAST': '3139',
+    'WANDIN NORTH': '3139',
+    'WOORI YALLOCK': '3139',
+    'YELLINGBO': '3139',
+    'CHAPEL STREET NORTH': '3141',
+    'DOMAIN ROAD PO': '3141',
+    'SOUTH YARRA': '3141',
+    'HAWKSBURN': '3142',
+    'TOORAK': '3142',
+    'ARMADALE': '3143',
+    'ARMADALE NORTH': '3143',
+    'KOOYONG': '3144',
+    'MALVERN': '3144',
+    'MALVERN NORTH': '3144',
+    'CAULFIELD EAST': '3145',
+    'CENTRAL PARK': '3145',
+    'DARLING': '3145',
+    'DARLING SOUTH': '3145',
+    'MALVERN EAST': '3145',
+    'WATTLETREE ROAD PO': '3145',
+    'GLEN IRIS': '3146',
+    'ASHBURTON': '3147',
+    'ASHWOOD': '3147',
+    'CHADSTONE': '3148',
+    'CHADSTONE CENTRE': '3148',
+    'HOLMESGLEN': '3148',
+    'MOUNT WAVERLEY': '3149',
+    'PINEWOOD': '3149',
+    'SYNDAL': '3149',
+    'BRANDON PARK': '3150',
+    'GLEN WAVERLEY': '3150',
+    'WHEELERS HILL': '3150',
+    'BURWOOD EAST': '3151',
+    'KNOX CITY CENTRE': '3152',
+    'STUDFIELD': '3152',
+    'WANTIRNA': '3152',
+    'WANTIRNA SOUTH': '3152',
+    'BAYSWATER': '3153',
+    'BAYSWATER NORTH': '3153',
+    'BORONIA': '3155',
+    'FERNTREE GULLY': '3156',
+    'LYSTERFIELD': '3156',
+    'LYSTERFIELD SOUTH': '3156',
+    'MOUNTAIN GATE': '3156',
+    'UPPER FERNTREE GULLY': '3156',
+    'UPWEY': '3158',
+    'MENZIES CREEK': '3159',
+    'SELBY': '3159',
+    'BELGRAVE': '3160',
+    'BELGRAVE HEIGHTS': '3160',
+    'BELGRAVE SOUTH': '3160',
+    'TECOMA': '3160',
+    'CAULFIELD JUNCTION': '3161',
+    'CAULFIELD NORTH': '3161',
+    'CAULFIELD': '3162',
+    'CAULFIELD SOUTH': '3162',
+    'HOPETOUN GARDENS': '3162',
+    'BOORAN ROAD PO': '3163',
+    'CARNEGIE': '3163',
+    'GLEN HUNTLY': '3163',
+    'MURRUMBEENA': '3163',
+    'BENTLEIGH EAST': '3165',
+    'COATESVILLE': '3165',
+    'HUGHESDALE': '3166',
+    'HUNTINGDALE': '3166',
+    'OAKLEIGH': '3166',
+    'OAKLEIGH EAST': '3166',
+    'OAKLEIGH SOUTH': '3167',
+    'CLAYTON': '3168',
+    'NOTTING HILL': '3168',
+    'CLARINDA': '3169',
+    'CLAYTON SOUTH': '3169',
+    'WAVERLEY GARDENS': '3170',
+    'SANDOWN VILLAGE': '3171',
+    'DINGLEY VILLAGE': '3172',
+    'SPRINGVALE SOUTH': '3172',
+    'KEYSBOROUGH': '3173',
+    'NOBLE PARK': '3174',
+    'NOBLE PARK EAST': '3174',
+    'NOBLE PARK NORTH': '3174',
+    'BANGHOLME': '3175',
+    'DANDENONG': '3175',
+    'DANDENONG EAST': '3175',
+    'DANDENONG NORTH': '3175',
+    'DANDENONG SOUTH': '3175',
+    'DUNEARN': '3175',
+    'DOVETON': '3177',
+    'EUMEMMERRING': '3177',
+    'ROWVILLE': '3178',
+    'SCORESBY': '3179',
+    'KNOXFIELD': '3180',
+    'PRAHRAN': '3181',
+    'PRAHRAN EAST': '3181',
+    'ST KILDA': '3182',
+    'ST KILDA SOUTH': '3182',
+    'ST KILDA WEST': '3182',
+    'BALACLAVA': '3183',
+    'ST KILDA EAST': '3183',
+    'BRIGHTON ROAD': '3184',
+    'ELWOOD': '3184',
+    'ELSTERNWICK': '3185',
+    'GARDENVALE': '3185',
+    'RIPPONLEA': '3185',
+    'BRIGHTON': '3186',
+    'BRIGHTON NORTH': '3186',
+    'DENDY': '3186',
+    'WERE STREET PO': '3186',
+    'BRIGHTON EAST': '3187',
+    'NORTH ROAD': '3187',
+    'HAMPTON EAST': '3188',
+    'HAMPTON NORTH': '3188',
+    'MOORABBIN': '3189',
+    'MOORABBIN EAST': '3189',
+    'WISHART': '3189',
+    'HIGHETT': '3190',
+    'CHELTENHAM EAST': '3192',
+    'CHELTENHAM NORTH': '3192',
+    'SOUTHLAND CENTRE': '3192',
+    'BEAUMARIS': '3193',
+    'BLACK ROCK': '3193',
+    'BLACK ROCK NORTH': '3193',
+    'MENTONE': '3194',
+    'MENTONE EAST': '3194',
+    'MOORABBIN AIRPORT': '3194',
+    'ASPENDALE': '3195',
+    'ASPENDALE GARDENS': '3195',
+    'BRAESIDE': '3195',
+    'MORDIALLOC': '3195',
+    'MORDIALLOC NORTH': '3195',
+    'PARKDALE': '3195',
+    'WATERWAYS': '3195',
+    'BONBEACH': '3196',
+    'CHELSEA': '3196',
+    'CHELSEA HEIGHTS': '3196',
+    'EDITHVALE': '3196',
+    'CARRUM': '3197',
+    'PATTERSON LAKES': '3197',
+    'BELVEDERE PARK': '3198',
+    'SEAFORD': '3198',
+    'FRANKSTON': '3199',
+    'FRANKSTON EAST': '3199',
+    'FRANKSTON HEIGHTS': '3199',
+    'FRANKSTON SOUTH': '3199',
+    'KARINGAL': '3199',
+    'KARINGAL CENTRE': '3199',
+    'FRANKSTON NORTH': '3200',
+    'PINES FOREST': '3200',
+    'CARRUM DOWNS': '3201',
+    'HEATHERTON': '3202',
+    'BENTLEIGH': '3204',
+    'MCKINNON': '3204',
+    'ORMOND': '3204',
+    'PATTERSON': '3204',
+    'SOUTH MELBOURNE': '3205',
+    'SOUTH MELBOURNE DC': '3205',
+    'ALBERT PARK': '3206',
+    'MIDDLE PARK': '3206',
+    'GARDEN CITY': '3207',
+    'PORT MELBOURNE': '3207',
+    'LARA': '3212',
+    'POINT WILSON': '3212',
+    'CORIO': '3214',
+    'NORLANE': '3214',
+    'BELL PARK': '3215',
+    'BELL POST HILL': '3215',
+    'DRUMCONDRA': '3215',
+    'GEELONG NORTH': '3215',
+    'HAMLYN HEIGHTS': '3215',
+    'NORTH GEELONG': '3215',
+    'RIPPLESIDE': '3215',
+    'FRESHWATER CREEK': '3216',
+    'GROVEDALE': '3216',
+    'GROVEDALE EAST': '3216',
+    'HIGHTON': '3216',
+    'MARSHALL': '3216',
+    'MOUNT DUNEED': '3216',
+    'WANDANA HEIGHTS': '3216',
+    'WAURN PONDS': '3216',
+    'DEAKIN UNIVERSITY': '3217',
+    'GEELONG WEST': '3218',
+    'HERNE HILL': '3218',
+    'MANIFOLD HEIGHTS': '3218',
+    'BREAKWATER': '3219',
+    'EAST GEELONG': '3219',
+    'NEWCOMB': '3219',
+    'ST ALBANS PARK': '3219',
+    'THOMSON': '3219',
+    'WHITTINGTON': '3219',
+    'BAREENA': '3220',
+    'GEELONG': '3220',
+    'SOUTH GEELONG': '3220',
+    'ANAKIE': '3221',
+    'BARRABOOL': '3221',
+    'BATESFORD': '3221',
+    'BELLARINE': '3221',
+    'CERES': '3221',
+    'FYANSFORD': '3221',
+    'GEELONG MC': '3221',
+    'GNARWARRE': '3221',
+    'GREY RIVER': '3221',
+    'KENNETT RIVER': '3221',
+    'LOVELY BANKS': '3221',
+    'MOOLAP': '3221',
+    'MOORABOOL': '3221',
+    'MURGHEBOLUC': '3221',
+    'SEPARATION CREEK': '3221',
+    'STAUGHTON VALE': '3221',
+    'STONEHAVEN': '3221',
+    'WALLINGTON': '3221',
+    'WONGARRA': '3221',
+    'WYE RIVER': '3221',
+    'CLIFTON SPRINGS': '3222',
+    'DRYSDALE': '3222',
+    'MANNERIM': '3222',
+    'MARCUS HILL': '3222',
+    'INDENTED HEAD': '3223',
+    'PORTARLINGTON': '3223',
+    'LEOPOLD': '3224',
+    'POINT LONSDALE': '3225',
+    'SWAN ISLAND': '3225',
+    'OCEAN GROVE': '3226',
+    'BARWON HEADS': '3227',
+    'BREAMLEA': '3227',
+    'CONNEWARRE': '3227',
+    'BELLBRAE': '3228',
+    'BELLS BEACH': '3228',
+    'JAN JUC': '3228',
+    'TORQUAY': '3228',
+    'ANGLESEA': '3230',
+    'AIREYS INLET': '3231',
+    'EASTERN VIEW': '3231',
+    'FAIRHAVEN': '3231',
+    'MOGGS CREEK': '3231',
+    'APOLLO BAY': '3233',
+    'CAPE OTWAY': '3233',
+    'PETTICOAT CREEK': '3233',
+    'SKENES CREEK': '3233',
+    'SKENES CREEK NORTH': '3233',
+    'BENWERRIN': '3235',
+    'BOONAH': '3235',
+    'DEANS MARSH': '3235',
+    'PENNYROYAL': '3235',
+    'MOUNT SABINE': '3236',
+    'AIRE VALLEY': '3237',
+    'BEECH FOREST': '3237',
+    'FERGUSON': '3237',
+    'GELLIBRAND LOWER': '3237',
+    'WATTLE HILL': '3237',
+    'WEEAPROINAH': '3237',
+    'WYELANGTA': '3237',
+    'YUULONG': '3237',
+    'GLENAIRE': '3238',
+    'HORDERN VALE': '3238',
+    'JOHANNA': '3238',
+    'LAVERS HILL': '3238',
+    'CARLISLE RIVER': '3239',
+    'CHAPPLE VALE': '3239',
+    'GELLIBRAND': '3239',
+    'KENNEDYS CREEK': '3239',
+    'BUCKLEY': '3240',
+    'GHERANG': '3240',
+    'MODEWARRE': '3240',
+    'MORIAC': '3240',
+    'MOUNT MORIAC': '3240',
+    'PARAPARAP': '3240',
+    'BAMBRA': '3241',
+    'OMBERSLEY': '3241',
+    'WENSLEYDALE': '3241',
+    'WINCHELSEA': '3241',
+    'WINCHELSEA SOUTH': '3241',
+    'WURDIBOLUC': '3241',
+    'BIRREGURRA': '3242',
+    'BARWON DOWNS': '3243',
+    'GERANGAMETE': '3243',
+    'MURROON': '3243',
+    'WARNCOORT': '3243',
+    'WHOOREL': '3243',
+    'ALVIE': '3249',
+    'BALINTORE': '3249',
+    'BARONGAROOK': '3249',
+    'BARONGAROOK WEST': '3249',
+    'BARRAMUNGA': '3249',
+    'CORAGULAC': '3249',
+    'CORUNNUN': '3249',
+    'DREEITE': '3249',
+    'DREEITE SOUTH': '3249',
+    'IRREWARRA': '3249',
+    'IRREWILLIPE': '3249',
+    'IRREWILLIPE EAST': '3249',
+    'KAWARREN': '3249',
+    'LARPENT': '3249',
+    'NALANGIL': '3249',
+    'ONDIT': '3249',
+    'PIRRON YALLOCK': '3249',
+    'POMBORNEIT EAST': '3249',
+    'SWAN MARSH': '3249',
+    'TANYBRYN': '3249',
+    'WARRION': '3249',
+    'WOOL WOOL': '3249',
+    'YEO': '3249',
+    'YEODENE': '3249',
+    'COLAC': '3250',
+    'COLAC EAST': '3250',
+    'COLAC WEST': '3250',
+    'ELLIMINYT': '3250',
+    'BEEAC': '3251',
+    'CUNDARE NORTH': '3251',
+    'EURACK': '3251',
+    'WEERING': '3251',
+    'COROROOKE': '3254',
+    'BOOKAAR': '3260',
+    'BOSTOCKS CREEK': '3260',
+    'BUNGADOR': '3260',
+    'CARPENDEIT': '3260',
+    'CHOCOLYN': '3260',
+    'GNOTUK': '3260',
+    'KARIAH': '3260',
+    'KOALLAH': '3260',
+    'LESLIE MANOR': '3260',
+    'POMBORNEIT': '3260',
+    'POMBORNEIT NORTH': '3260',
+    'SKIBO': '3260',
+    'SOUTH PURRUMBETE': '3260',
+    'STONYFORD': '3260',
+    'TANDAROOK': '3260',
+    'TESBURY': '3260',
+    'WEERITE': '3260',
+    'TERANG': '3264',
+    'BOORCAN': '3265',
+    'CUDGEE': '3265',
+    'DIXIE': '3265',
+    'ECKLIN SOUTH': '3265',
+    'FRAMLINGHAM': '3265',
+    'FRAMLINGHAM EAST': '3265',
+    'GARVOC': '3265',
+    'GLENORMISTON NORTH': '3265',
+    'GLENORMISTON SOUTH': '3265',
+    'KOLORA': '3265',
+    'LAANG': '3265',
+    'NOORAT': '3265',
+    'NOORAT EAST': '3265',
+    'PANMURE': '3265',
+    'TAROON': '3265',
+    'THE SISTERS': '3265',
+    'BULLAHARRE': '3266',
+    'COBDEN': '3266',
+    'COBRICO': '3266',
+    'ELINGAMITE': '3266',
+    'ELINGAMITE NORTH': '3266',
+    'GLENFYNE': '3266',
+    'JANCOURT': '3266',
+    'JANCOURT EAST': '3266',
+    'NAROGHID': '3266',
+    'AYRFORD': '3268',
+    'BRUCKNELL': '3268',
+    'COORIEMUNGLE': '3268',
+    'COWLEYS CREEK': '3268',
+    'CURDIES RIVER': '3268',
+    'CURDIEVALE': '3268',
+    'HEYTESBURY LOWER': '3268',
+    'NEWFIELD': '3268',
+    'NIRRANDA': '3268',
+    'NIRRANDA EAST': '3268',
+    'NIRRANDA SOUTH': '3268',
+    'NULLAWARRE': '3268',
+    'NULLAWARRE NORTH': '3268',
+    'PAARATTE': '3268',
+    'THE COVE': '3268',
+    'TIMBOON': '3268',
+    'TIMBOON WEST': '3268',
+    'PORT CAMPBELL': '3269',
+    'PRINCETOWN': '3269',
+    'WAARRE': '3269',
+    'PETERBOROUGH': '3270',
+    'DUNDONNELL': '3271',
+    'PURA PURA': '3271',
+    'WOORNDOO': '3272',
+    'CARAMUT': '3274',
+    'MAILORS FLAT': '3275',
+    'MINJAH': '3276',
+    'WOOLSTHORPE': '3276',
+    'ALLANSFORD': '3277',
+    'MEPUNGA': '3277',
+    'MEPUNGA EAST': '3277',
+    'MEPUNGA WEST': '3277',
+    'NARINGAL': '3277',
+    'NARINGAL EAST': '3277',
+    'PURNIM': '3278',
+    'PURNIM WEST': '3278',
+    'BALLANGEICH': '3279',
+    'WANGOOM': '3279',
+    'DENNINGTON': '3280',
+    'WARRNAMBOOL': '3280',
+    'WARRNAMBOOL EAST': '3280',
+    'WARRNAMBOOL WEST': '3280',
+    'BUSHFIELD': '3281',
+    'GRASSMERE': '3281',
+    'WINSLOW': '3281',
+    'ILLOWA': '3282',
+    'KOROIT': '3282',
+    'CROSSLEY': '3283',
+    'KILLARNEY': '3283',
+    'KIRKSTALL': '3283',
+    'SOUTHERN CROSS': '3283',
+    'TARRONE': '3283',
+    'TOWER HILL': '3283',
+    'WARRONG': '3283',
+    'WILLATOOK': '3283',
+    'YANGERY': '3283',
+    'YARPTURK': '3283',
+    'ORFORD': '3284',
+    'PORT FAIRY': '3284',
+    'NARRAWONG': '3285',
+    'ST HELENS': '3285',
+    'TOOLONG': '3285',
+    'TYRENDARRA': '3285',
+    'TYRENDARRA EAST': '3285',
+    'YAMBUK': '3285',
+    'CONDAH SWAMP': '3286',
+    'KNEBSWORTH': '3286',
+    'RIPPONHURST': '3286',
+    'WARRABKOOK': '3286',
+    'HAWKESDALE': '3287',
+    'MINHAMITE': '3287',
+    'GAZETTE': '3289',
+    'GERRIGERRUP': '3289',
+    'PURDEET': '3289',
+    'TABOR': '3289',
+    'GLENTHOMPSON': '3293',
+    'NAREEB': '3293',
+    'NARRAPUMELAP SOUTH': '3293',
+    'KARABEAL': '3294',
+    'MIRRANATWA': '3294',
+    'MOUTAJUP': '3294',
+    'VICTORIA POINT': '3294',
+    'VICTORIA VALLEY': '3294',
+    'WOODHOUSE': '3294',
+    'BYADUK NORTH': '3300',
+    'BOCHARA': '3301',
+    'BUCKLEY SWAMP': '3301',
+    'BYADUK': '3301',
+    'CROXTON EAST': '3301',
+    'HENSLEY PARK': '3301',
+    'MORGIANA': '3301',
+    'MOUNT NAPIER': '3301',
+    'STRATHKELLAR': '3301',
+    'TAHARA': '3301',
+    'TARRINGTON': '3301',
+    'WANNON': '3301',
+    'WARRAYURE': '3301',
+    'YATCHAW': '3301',
+    'YULECART': '3301',
+    'BRANXHOLME': '3302',
+    'GRASSDALE': '3302',
+    'BREAKAWAY CREEK': '3303',
+    'CONDAH': '3303',
+    'HOTSPUR': '3303',
+    'LAKE CONDAH': '3303',
+    'WALLACEDALE': '3303',
+    'BESSIEBELLE': '3304',
+    'DARTMOOR': '3304',
+    'DRIK DRIK': '3304',
+    'DRUMBORG': '3304',
+    'GREENWALD': '3304',
+    'HEYWOOD': '3304',
+    'HOMERTON': '3304',
+    'MILLTOWN': '3304',
+    'MUMBANNAR': '3304',
+    'MYAMYN': '3304',
+    'WINNAP': '3304',
+    'ALLESTREE': '3305',
+    'CAPE BRIDGEWATER': '3305',
+    'CASHMORE': '3305',
+    'DUTTON WAY': '3305',
+    'GORAE': '3305',
+    'GORAE WEST': '3305',
+    'HEATHMERE': '3305',
+    'MOUNT RICHMOND': '3305',
+    'PORTLAND NORTH': '3305',
+    'PORTLAND WEST': '3305',
+    'DIGBY': '3309',
+    'MERINO': '3310',
+    'TAHARA WEST': '3310',
+    'CASTERTON': '3311',
+    'BAHGALLAH': '3312',
+    'BRIMBOAL': '3312',
+    'CARAPOOK': '3312',
+    'CHETWYND': '3312',
+    'DERGHOLM': '3312',
+    'DORODONG': '3312',
+    'DUNROBIN': '3312',
+    'LAKE MUNDI': '3312',
+    'LINDSAY': '3312',
+    'NANGEELA': '3312',
+    'POOLAIJELO': '3312',
+    'POWERS CREEK': '3312',
+    'SANDFORD': '3312',
+    'STRATHDOWNIE': '3312',
+    'WANDO BRIDGE': '3312',
+    'WANDO VALE': '3312',
+    'WARROCK': '3312',
+    'BULART': '3314',
+    'CAVENDISH': '3314',
+    'GLENISLA': '3314',
+    'GRAMPIANS': '3314',
+    'MOORALLA': '3314',
+    'WOOHLPOOER': '3314',
+    'BRIT BRIT': '3315',
+    'CLOVER FLAT': '3315',
+    'COLERAINE': '3315',
+    'COOJAR': '3315',
+    'CULLA': '3315',
+    'GRINGEGALGONA': '3315',
+    'GRITJURK': '3315',
+    'HILGAY': '3315',
+    'KONONGWOOTONG': '3315',
+    'MELVILLE FOREST': '3315',
+    'MUNTHAM': '3315',
+    'NAREEN': '3315',
+    'PASCHENDALE': '3315',
+    'TAHARA BRIDGE': '3315',
+    'TARRAYOUKYAN': '3315',
+    'TARRENLEA': '3315',
+    'WOOTONG VALE': '3315',
+    'HARROW': '3317',
+    'CHARAM': '3318',
+    'CONNEWIRRICOO': '3318',
+    'EDENHOPE': '3318',
+    'KADNOOK': '3318',
+    'LANGKOOP': '3318',
+    'PATYAH': '3318',
+    'ULLSWATER': '3318',
+    'BENAYEO': '3319',
+    'BRINGALBERT': '3319',
+    'HESSE': '3321',
+    'INVERLEIGH': '3321',
+    'WINGEEL': '3321',
+    'CRESSY': '3322',
+    'BERRYBANK': '3323',
+    'DUVERNEY': '3323',
+    'FOXHOW': '3323',
+    'MOUNT BUTE': '3324',
+    'DERRINALLUM': '3325',
+    'LARRALEA': '3325',
+    'VITE VITE': '3325',
+    'VITE VITE NORTH': '3325',
+    'TEESDALE': '3328',
+    'BARUNAH PARK': '3329',
+    'SHELFORD': '3329',
+    'ROKEWOOD': '3330',
+    'BANNOCKBURN': '3331',
+    'GHERINGHAP': '3331',
+    'RUSSELLS BRIDGE': '3331',
+    'SHE OAKS': '3331',
+    'STEIGLITZ': '3331',
+    'SUTHERLANDS CREEK': '3331',
+    'LETHBRIDGE': '3332',
+    'BAMGANIE': '3333',
+    'MEREDITH': '3333',
+    'BUNGAL': '3334',
+    'CARGERIE': '3334',
+    'ELAINE': '3334',
+    'MORRISONS': '3334',
+    'MOUNT DORAN': '3334',
+    'ROCKBANK': '3335',
+    'KURUNJANG': '3337',
+    'MELTON': '3337',
+    'MELTON WEST': '3337',
+    'TOOLERN VALE': '3337',
+    'EXFORD': '3338',
+    'EYNESBURY': '3338',
+    'MELTON SOUTH': '3338',
+    'BACCHUS MARSH': '3340',
+    'BALLIANG': '3340',
+    'BALLIANG EAST': '3340',
+    'COIMADAI': '3340',
+    'DARLEY': '3340',
+    'HOPETOUN PARK': '3340',
+    'LONG FOREST': '3340',
+    'MADDINGLEY': '3340',
+    'MERRIMU': '3340',
+    'PARWAN': '3340',
+    'ROWSLEY': '3340',
+    'DALES CREEK': '3341',
+    'KOROBEIT': '3341',
+    'MYRNIONG': '3341',
+    'PENTLAND HILLS': '3341',
+    'BALLAN': '3342',
+    'BEREMBOKE': '3342',
+    'BLAKEVILLE': '3342',
+    'BUNDING': '3342',
+    'COLBROOK': '3342',
+    'DURDIDWARRAH': '3342',
+    'FISKVILLE': '3342',
+    'INGLISTON': '3342',
+    'MOUNT WALLACE': '3342',
+    'ALFREDTON': '3350',
+    'BAKERY HILL': '3350',
+    'BALLARAT': '3350',
+    'BALLARAT CENTRAL': '3350',
+    'BALLARAT EAST': '3350',
+    'BALLARAT NORTH': '3350',
+    'BALLARAT WEST': '3350',
+    'BROWN HILL': '3350',
+    'CANADIAN': '3350',
+    'GOLDEN POINT': '3350',
+    'INVERMAY PARK': '3350',
+    'LAKE WENDOUREE': '3350',
+    'MOUNT CLEAR': '3350',
+    'MOUNT HELEN': '3350',
+    'NERRINA': '3350',
+    'REDAN': '3350',
+    'SOLDIERS HILL': '3350',
+    'SOVEREIGN HILL': '3350',
+    'BERRINGA': '3351',
+    'BO PEEP': '3351',
+    'CAPE CLEAR': '3351',
+    'CARNGHAM': '3351',
+    'CHEPSTOWE': '3351',
+    'HADDON': '3351',
+    'HILLCREST': '3351',
+    'ILLABAROOK': '3351',
+    'LAKE BOLAC': '3351',
+    'MININERA': '3351',
+    'MOUNT EMU': '3351',
+    'NERRIN NERRIN': '3351',
+    'NINTINGBOOL': '3351',
+    'PIGGOREET': '3351',
+    'PITFIELD': '3351',
+    'ROKEWOOD JUNCTION': '3351',
+    'ROSS CREEK': '3351',
+    'SCARSDALE': '3351',
+    'SMYTHES CREEK': '3351',
+    'SMYTHESDALE': '3351',
+    'SNAKE VALLEY': '3351',
+    'SPRINGDALLAH': '3351',
+    'STAFFORDSHIRE REEF': '3351',
+    'STREATHAM': '3351',
+    'WALLINDUC': '3351',
+    'WESTMERE': '3351',
+    'ADDINGTON': '3352',
+    'BALLARAT ROADSIDE DELIVERY': '3352',
+    'BLOWHARD': '3352',
+    'BOLWARRAH': '3352',
+    'BREWSTER': '3352',
+    'BULLAROOK': '3352',
+    'BUNGAREE': '3352',
+    'BUNKERS HILL': '3352',
+    'BURRUMBEET': '3352',
+    'CAMBRIAN HILL': '3352',
+    'CARDIGAN': '3352',
+    'CARDIGAN VILLAGE': '3352',
+    'CHAPEL FLAT': '3352',
+    'CLARETOWN': '3352',
+    'CLARKES HILL': '3352',
+    'CORINDHAP': '3352',
+    'DEREEL': '3352',
+    'DUNNSTOWN': '3352',
+    'DURHAM LEAD': '3352',
+    'ERCILDOUNE': '3352',
+    'GARIBALDI': '3352',
+    'GLEN PARK': '3352',
+    'GLENBRAE': '3352',
+    'GONG GONG': '3352',
+    'GRENVILLE': '3352',
+    'INVERMAY': '3352',
+    'LAL LAL': '3352',
+    'LAMPLOUGH': '3352',
+    'LANGI KAL KAL': '3352',
+    'LEARMONTH': '3352',
+    'LEIGH CREEK': '3352',
+    'LEXTON': '3352',
+    'MAGPIE': '3352',
+    'MILLBROOK': '3352',
+    'MINERS REST': '3352',
+    'MITCHELL PARK': '3352',
+    'MOLLONGGHIP': '3352',
+    'MOUNT BOLTON': '3352',
+    'MOUNT EGERTON': '3352',
+    'MOUNT MERCER': '3352',
+    'MOUNT ROWAN': '3352',
+    'NAPOLEONS': '3352',
+    'NAVIGATORS': '3352',
+    'POOTILLA': '3352',
+    'SCOTCHMANS LEAD': '3352',
+    'SCOTSBURN': '3352',
+    'SPRINGBANK': '3352',
+    'SULKY': '3352',
+    'WALLACE': '3352',
+    'WARRENHEIP': '3352',
+    'WAUBRA': '3352',
+    'WEATHERBOARD': '3352',
+    'WERNETH': '3352',
+    'YENDON': '3352',
+    'LAKE GARDENS': '3355',
+    'WENDOUREE': '3355',
+    'WENDOUREE VILLAGE': '3355',
+    'DELACOMBE': '3356',
+    'BUNINYONG': '3357',
+    'SCOTSMANS LEAD': '3357',
+    'HAPPY VALLEY': '3360',
+    'LINTON': '3360',
+    'MANNIBADAR': '3360',
+    'PITTONG': '3360',
+    'WILLOWVALE': '3360',
+    'BRADVALE': '3361',
+    'CARRANBALLAC': '3361',
+    'SKIPTON': '3361',
+    'CRESWICK': '3363',
+    'CRESWICK NORTH': '3363',
+    'DEAN': '3363',
+    'GLENDARUEL': '3363',
+    'LANGDONS HILL': '3363',
+    'MOUNT BECKWORTH': '3363',
+    'TOURELLO': '3363',
+    'ALLENDALE': '3364',
+    'ASCOT': '3364',
+    'BARKSTEAD': '3364',
+    'BLAMPIED': '3364',
+    'BROOMFIELD': '3364',
+    'CABBAGE TREE': '3364',
+    'COGHILLS CREEK': '3364',
+    'GLENDONNELL': '3364',
+    'KOOROOCHEANG': '3364',
+    'MOUNT PROSPECT': '3364',
+    'NEWLYN': '3364',
+    'NEWLYN NORTH': '3364',
+    'ROCKLYN': '3364',
+    'SMEATON': '3364',
+    'SMOKEYTOWN': '3364',
+    'SPRINGMOUNT': '3364',
+    'STRATHLEA': '3364',
+    'WERONA': '3364',
+    'GLENGOWER': '3370',
+    'MOUNT CAMERON': '3370',
+    'ULLINA': '3370',
+    'AMHERST': '3371',
+    'BURNBANK': '3371',
+    'CARALULUP': '3371',
+    'DUNACH': '3371',
+    'EVANSFORD': '3371',
+    'LILLICUR': '3371',
+    'MOUNT GLASGOW': '3371',
+    'RED LION': '3371',
+    'TALBOT': '3371',
+    'BEAUFORT': '3373',
+    'CHUTE': '3373',
+    'LAKE GOLDSMITH': '3373',
+    'LAKE WONGAN': '3373',
+    'MAIN LEAD': '3373',
+    'MENA PARK': '3373',
+    'NERRING': '3373',
+    'SHIRLEY': '3373',
+    'STOCKYARD HILL': '3373',
+    'STONELEIGH': '3373',
+    'TRAWALLA': '3373',
+    'BALLYROGAN': '3375',
+    'BAYINDEEN': '3375',
+    'BUANGOR': '3375',
+    'MIDDLE CREEK': '3375',
+    'ARARAT': '3377',
+    'ARMSTRONG': '3377',
+    'BULGANA': '3377',
+    'CROWLANDS': '3377',
+    'DENICULL CREEK': '3377',
+    'DOBIE': '3377',
+    'DUNNEWORTHY': '3377',
+    'EVERSLEY': '3377',
+    'GREAT WESTERN': '3377',
+    'LANGI LOGAN': '3377',
+    'MAROONA': '3377',
+    'MOUNT COLE': '3377',
+    'MOUNT COLE CREEK': '3377',
+    'MOYSTON': '3377',
+    'NORVAL': '3377',
+    'RHYMNEY': '3377',
+    'ROSSBRIDGE': '3377',
+    'WARRAK': '3377',
+    'TATYOON': '3378',
+    'YALLAYPOORA': '3378',
+    'BORNES HILL': '3379',
+    'MAFEKING': '3379',
+    'STAVELY': '3379',
+    'WICKLIFFE': '3379',
+    'WILLAURA': '3379',
+    'WILLAURA NORTH': '3379',
+    'BELLELLEN': '3380',
+    'BRIDGE INN': '3380',
+    'MOKEPILLY': '3380',
+    'STAWELL': '3380',
+    'STAWELL WEST': '3380',
+    'WINJALLOK': '3380',
+    'BARKLY': '3381',
+    'BOLANGUM': '3381',
+    'CALLAWADDA': '3381',
+    'CAMPBELLS BRIDGE': '3381',
+    'CONCONGELLA': '3381',
+    'DEEP LEAD': '3381',
+    'FYANS CREEK': '3381',
+    'GERMANIA': '3381',
+    'GREENS CREEK': '3381',
+    'HALLS GAP': '3381',
+    'ILLAWARRA': '3381',
+    'JOEL JOEL': '3381',
+    'JOEL SOUTH': '3381',
+    'KANYA': '3381',
+    'LAKE FYANS': '3381',
+    'LAKE LONSDALE': '3381',
+    'LUBECK': '3381',
+    'MORRL MORRL': '3381',
+    'POMONAL': '3381',
+    'ROSTRON': '3381',
+    'WAL WAL': '3381',
+    'WALLALOO': '3381',
+    'WALLALOO EAST': '3381',
+    'FRENCHMANS': '3384',
+    'LANDSBOROUGH': '3384',
+    'LANDSBOROUGH WEST': '3384',
+    'NAVARRE': '3384',
+    'SHAYS FLAT': '3384',
+    'WATTLE CREEK': '3384',
+    'DADSWELLS BRIDGE': '3385',
+    'GLENORCHY': '3385',
+    'LEDCOURT': '3385',
+    'RIACHELLA': '3385',
+    'MARNOO': '3387',
+    'BANYENA': '3388',
+    'RUPANYUP': '3388',
+    'KEWELL': '3390',
+    'MURTOA': '3390',
+    'BRIM': '3391',
+    'MINYIP': '3392',
+    'SHEEP HILLS': '3392',
+    'AUBREY': '3393',
+    'BANGERANG': '3393',
+    'CANNUM': '3393',
+    'CRYMELON': '3393',
+    'KELLALAC': '3393',
+    'LAH': '3393',
+    'WARRACKNABEAL': '3393',
+    'WILKUR': '3393',
+    'WILLENABRINA': '3393',
+    'BEULAH': '3395',
+    'KENMARE': '3395',
+    'REEDY DAM': '3395',
+    'HOPETOUN': '3396',
+    'HORSHAM': '3400',
+    'HORSHAM WEST': '3400',
+    'BRIMPAEN': '3401',
+    'BUNGALALLY': '3401',
+    'CHERRYPOOL': '3401',
+    'DOOEN': '3401',
+    'DRUNG': '3401',
+    'GYMBOWEN': '3401',
+    'HAVEN': '3401',
+    'JUNG': '3401',
+    'KALKEE': '3401',
+    'KANAGULK': '3401',
+    'KARNAK': '3401',
+    'LAHARUM': '3401',
+    'LONGERENONG': '3401',
+    'LOWER NORTON': '3401',
+    'MCKENZIE CREEK': '3401',
+    'MOCKINYA': '3401',
+    'MURRA WARRA': '3401',
+    'NURCOUNG': '3401',
+    'NURRABIEL': '3401',
+    'PIMPINIO': '3401',
+    'QUANTONG': '3401',
+    'ROCKLANDS': '3401',
+    'ST HELENS PLAINS': '3401',
+    'TELANGATUK EAST': '3401',
+    'TOOLONDO': '3401',
+    'VECTIS': '3401',
+    'WAIL': '3401',
+    'WALLUP': '3401',
+    'WARTOOK': '3401',
+    'WONWONDAH': '3401',
+    'ZUMSTEINS': '3401',
+    'ENGLEFIELD': '3407',
+    'GATUM': '3407',
+    'PIGEON PONDS': '3407',
+    'VASEY': '3407',
+    'ARAPILES': '3409',
+    'CLEAR LAKE': '3409',
+    'DOUGLAS': '3409',
+    'DUCHEMBEGARRA': '3409',
+    'GRASS FLAT': '3409',
+    'JILPANGER': '3409',
+    'MIGA LAKE': '3409',
+    'MITRE': '3409',
+    'NATIMUK': '3409',
+    'NORADJUHA': '3409',
+    'TOOAN': '3409',
+    'WOMBELANO': '3409',
+    'GOROKE': '3412',
+    'MINIMAY': '3413',
+    'NEUARPURR': '3413',
+    'OZENKADNOOK': '3413',
+    'PERONNE': '3413',
+    'ANTWERP': '3414',
+    'DIMBOOLA': '3414',
+    'TARRANYURK': '3414',
+    'MIRAM': '3415',
+    'GERANG GERUNG': '3418',
+    'GLENLEE': '3418',
+    'KIATA': '3418',
+    'LAWLOIT': '3418',
+    'LITTLE DESERT': '3418',
+    'LORQUON': '3418',
+    'NETHERBY': '3418',
+    'NHILL': '3418',
+    'YANAC': '3418',
+    'KANIVA': '3419',
+    'LILLIMUR': '3420',
+    'SERVICETON': '3420',
+    'TELOPEA DOWNS': '3420',
+    'JEPARIT': '3423',
+    'ALBACUTYA': '3424',
+    'RAINBOW': '3424',
+    'YAAPEET': '3424',
+    'DIGGERS REST': '3427',
+    'SUNBURY': '3429',
+    'WILDWOOD': '3429',
+    'CLARKEFIELD': '3430',
+    'RIDDELLS CREEK': '3431',
+    'BOLINDA': '3432',
+    'MONEGEETTA': '3433',
+    'CHEROKEE': '3434',
+    'KERRIE': '3434',
+    'ROMSEY': '3434',
+    'BENLOCH': '3435',
+    'GOLDIE': '3435',
+    'LANCEFIELD': '3435',
+    'NULLA VALE': '3435',
+    'BULLENGAROOK': '3437',
+    'GISBORNE': '3437',
+    'GISBORNE SOUTH': '3437',
+    'NEW GISBORNE': '3438',
+    'MACEDON': '3440',
+    'MOUNT MACEDON': '3441',
+    'ASHBOURNE': '3442',
+    'CADELLO': '3442',
+    'CARLSRUHE': '3442',
+    'COBAW': '3442',
+    'HESKET': '3442',
+    'NEWHAM': '3442',
+    'ROCHFORD': '3442',
+    'WOODEND': '3442',
+    'WOODEND NORTH': '3442',
+    'BARFOLD': '3444',
+    'BAYNTON': '3444',
+    'BAYNTON EAST': '3444',
+    'EDGECOMBE': '3444',
+    'GLENHOPE': '3444',
+    'KYNETON': '3444',
+    'KYNETON SOUTH': '3444',
+    'LANGLEY': '3444',
+    'LAURISTON': '3444',
+    'LYAL': '3444',
+    'METCALFE EAST': '3444',
+    'MIA MIA': '3444',
+    'PASTORIA': '3444',
+    'PASTORIA EAST': '3444',
+    'PIPERS CREEK': '3444',
+    'REDESDALE': '3444',
+    'SIDONIA': '3444',
+    'TYLDEN': '3444',
+    'TYLDEN SOUTH': '3444',
+    'DRUMMOND NORTH': '3446',
+    'MALMSBURY': '3446',
+    'ELPHINSTONE': '3448',
+    'METCALFE': '3448',
+    'SUTTON GRANGE': '3448',
+    'CASTLEMAINE': '3450',
+    'MOONLIGHT FLAT': '3450',
+    'BARKERS CREEK': '3451',
+    'CAMPBELLS CREEK': '3451',
+    'CHEWTON': '3451',
+    'CHEWTON BUSHLANDS': '3451',
+    'FARADAY': '3451',
+    'FRYERSTOWN': '3451',
+    'GLENLUCE': '3451',
+    'GOWER': '3451',
+    'IRISHTOWN': '3451',
+    'MCKENZIE HILL': '3451',
+    'MUCKLEFORD': '3451',
+    'TARILTA': '3451',
+    'VAUGHAN': '3451',
+    'YAPEEN': '3451',
+    'HARCOURT': '3453',
+    'HARCOURT NORTH': '3453',
+    'RAVENSWOOD SOUTH': '3453',
+    'BARRYS REEF': '3458',
+    'BLACKWOOD': '3458',
+    'FERN HILL': '3458',
+    'LERDERDERG': '3458',
+    'LITTLE HAMPTON': '3458',
+    'NEWBURY': '3458',
+    'NORTH BLACKWOOD': '3458',
+    'TRENTHAM': '3458',
+    'TRENTHAM EAST': '3458',
+    'BASALT': '3460',
+    'DAYLESFORD': '3460',
+    'BULLARTO': '3461',
+    'BULLARTO SOUTH': '3461',
+    'COOMOORA': '3461',
+    'DENVER': '3461',
+    'DRUMMOND': '3461',
+    'DRY DIGGINGS': '3461',
+    'EGANSTOWN': '3461',
+    'ELEVATED PLAINS': '3461',
+    'FRANKLINFORD': '3461',
+    'GLENLYON': '3461',
+    'HEPBURN': '3461',
+    'HEPBURN SPRINGS': '3461',
+    'KORWEINGUBOORA': '3461',
+    'LEONARDS HILL': '3461',
+    'LYONVILLE': '3461',
+    'MOUNT FRANKLIN': '3461',
+    'MUSK': '3461',
+    'MUSK VALE': '3461',
+    'PORCUPINE RIDGE': '3461',
+    'SAILORS FALLS': '3461',
+    'SAILORS HILL': '3461',
+    'SHEPHERDS FLAT': '3461',
+    'SPARGO CREEK': '3461',
+    'STRANGWAYS': '3461',
+    'WHEATSHEAF': '3461',
+    'YANDOIT': '3461',
+    'YANDOIT HILLS': '3461',
+    'JOYCES CREEK': '3462',
+    'MUCKLEFORD SOUTH': '3462',
+    'WELSHMANS REEF': '3462',
+    'BARINGHUP': '3463',
+    'BARINGHUP WEST': '3463',
+    'BRADFORD': '3463',
+    'EASTVILLE': '3463',
+    'LAANECOORIE': '3463',
+    'NEEREMAN': '3463',
+    'NUGGETTY': '3463',
+    'PERKINS REEF': '3463',
+    'PORCUPINE FLAT': '3463',
+    'SHELBOURNE': '3463',
+    'TARRENGOWER': '3463',
+    'WOODSTOCK WEST': '3463',
+    'CARISBROOK': '3464',
+    'ADELAIDE LEAD': '3465',
+    'ALMA': '3465',
+    'BOWENVALE': '3465',
+    'BUNG BONG': '3465',
+    'COTSWOLD': '3465',
+    'DAISY HILL': '3465',
+    'FLAGSTAFF': '3465',
+    'HAVELOCK': '3465',
+    'MAJORCA': '3465',
+    'MARYBOROUGH': '3465',
+    'MOOLORT': '3465',
+    'NATTE YALLOCK': '3465',
+    'RATHSCAR': '3465',
+    'RATHSCAR WEST': '3465',
+    'RODBOROUGH': '3465',
+    'SIMSON': '3465',
+    'TIMOR WEST': '3465',
+    'WAREEK': '3465',
+    'MOYREISK': '3467',
+    'AMPHITHEATRE': '3468',
+    'MOUNT LONARCH': '3468',
+    'ELMHURST': '3469',
+    'GLENLOFTY': '3469',
+    'GLENLOGIE': '3469',
+    'GLENPATRICK': '3469',
+    'NOWHERE CREEK': '3469',
+    'BET BET': '3472',
+    'BETLEY': '3472',
+    'BROMLEY': '3472',
+    'DUNLUCE': '3472',
+    'EDDINGTON': '3472',
+    'GOLDSBOROUGH': '3472',
+    'INKERMAN': '3472',
+    'MCINTYRE': '3472',
+    'MOLIAGUL': '3472',
+    'MOUNT HOOGHLY': '3472',
+    'ARCHDALE': '3475',
+    'ARCHDALE JUNCTION': '3475',
+    'BEALIBA': '3475',
+    'BURKES FLAT': '3475',
+    'COCHRANES CREEK': '3475',
+    'EMU': '3475',
+    'LOGAN': '3475',
+    'AVON PLAINS': '3478',
+    'BEAZLEYS BRIDGE': '3478',
+    'CARAPOOEE': '3478',
+    'CARAPOOEE WEST': '3478',
+    'COONOOER BRIDGE': '3478',
+    'COONOOER WEST': '3478',
+    'DARKBONEE': '3478',
+    'DOOBOOBETIC': '3478',
+    'ELBERTON': '3478',
+    'GOOROC': '3478',
+    'GOWAR EAST': '3478',
+    'GRE GRE': '3478',
+    'GRE GRE NORTH': '3478',
+    'GRE GRE SOUTH': '3478',
+    'KOOREH': '3478',
+    'MEDLYN': '3478',
+    'MITCHELLS HILL': '3478',
+    'MOOLERR': '3478',
+    'MOONAMBEL': '3478',
+    'PERCYDALE': '3478',
+    'SLATY CREEK': '3478',
+    'ST ARNAUD': '3478',
+    'ST ARNAUD EAST': '3478',
+    'ST ARNAUD NORTH': '3478',
+    'STUART MILL': '3478',
+    'SWANWATER': '3478',
+    'TANWOOD': '3478',
+    'TOTTINGTON': '3478',
+    'TRAYNORS LAGOON': '3478',
+    'TULKARA': '3478',
+    'WARRENMANG': '3478',
+    'YAWONG HILLS': '3478',
+    'AREEGRA': '3480',
+    'BANYENONG': '3480',
+    'BOOLITE': '3480',
+    'CARRON': '3480',
+    'COPE COPE': '3480',
+    'CORACK': '3480',
+    'CORACK EAST': '3480',
+    'DONALD': '3480',
+    'GIL GIL': '3480',
+    'JEFFCOTT': '3480',
+    'JEFFCOTT NORTH': '3480',
+    'LAEN': '3480',
+    'LAEN EAST': '3480',
+    'LAEN NORTH': '3480',
+    'LAKE BULOKE': '3480',
+    'LAWLER': '3480',
+    'LITCHFIELD': '3480',
+    'RICH AVON': '3480',
+    'RICH AVON EAST': '3480',
+    'RICH AVON WEST': '3480',
+    'SWANWATER WEST': '3480',
+    'MASSEY': '3482',
+    'MORTON PLAINS': '3482',
+    'WARMUR': '3482',
+    'WATCHEM': '3482',
+    'WATCHEM WEST': '3482',
+    'BALLAPUR': '3483',
+    'BIRCHIP': '3483',
+    'BIRCHIP WEST': '3483',
+    'CURYO': '3483',
+    'JIL JIL': '3483',
+    'KARYRIE': '3483',
+    'KINNABULLA': '3483',
+    'MARLBED': '3483',
+    'NARRAPORT': '3483',
+    'WHIRILY': '3483',
+    'BANYAN': '3485',
+    'WATCHUPGA': '3485',
+    'WILLANGIE': '3485',
+    'WOOMELANG': '3485',
+    'LASCELLES': '3487',
+    'SPEED': '3488',
+    'TURRIFF EAST': '3488',
+    'TEMPY': '3489',
+    'BOINKA': '3490',
+    'OUYEN': '3490',
+    'TORRITA': '3490',
+    'TUTYE': '3490',
+    'PATCHEWOLLOCK': '3491',
+    'CARWARP': '3494',
+    'COLIGNAN': '3494',
+    'IRAAK': '3494',
+    'NANGILOC': '3494',
+    'CARDROSS': '3496',
+    'CULLULLERAINE': '3496',
+    'MERINGUR': '3496',
+    'MERRINEE': '3496',
+    'NEDS CORNER': '3496',
+    'RED CLIFFS': '3496',
+    'SUNNYCLIFFS': '3496',
+    'WERRIMULL': '3496',
+    'MILDURA': '3500',
+    'MILDURA EAST': '3500',
+    'MILDURA WEST': '3500',
+    'PARINGI': '3500',
+    'HATTAH': '3501',
+    'KOORLONG': '3501',
+    'MILDURA CENTRE PLAZA': '3501',
+    'MILDURA SOUTH': '3501',
+    'NICHOLS POINT': '3501',
+    'BIRDWOODTON': '3505',
+    'MERBEIN': '3505',
+    'MERBEIN SOUTH': '3505',
+    'MERBEIN WEST': '3505',
+    'WARGAN': '3505',
+    'YELTA': '3505',
+    'COWANGIE': '3506',
+    'WALPEUP': '3507',
+    'LINGA': '3509',
+    'UNDERBOOL': '3509',
+    'CARINA': '3512',
+    'MURRAYVILLE': '3512',
+    'MARONG': '3515',
+    'WILSONS HILL': '3515',
+    'BRIDGEWATER': '3516',
+    'BRIDGEWATER NORTH': '3516',
+    'BRIDGEWATER ON LODDON': '3516',
+    'DERBY': '3516',
+    'LEICHARDT': '3516',
+    'YARRABERB': '3516',
+    'BEARS LAGOON': '3517',
+    'BRENANAH': '3517',
+    'GLENALBYN': '3517',
+    'INGLEWOOD': '3517',
+    'JARKLIN': '3517',
+    'KINGOWER': '3517',
+    'KURTING': '3517',
+    'POWLETT PLAINS': '3517',
+    'RHEOLA': '3517',
+    'SALISBURY WEST': '3517',
+    'SERPENTINE': '3517',
+    'BERRIMAL': '3518',
+    'BORUNG': '3518',
+    'FENTONS CREEK': '3518',
+    'FERNIHURST': '3518',
+    'FIERY FLAT': '3518',
+    'KURRACA': '3518',
+    'KURRACA WEST': '3518',
+    'MYSIA': '3518',
+    'NINE MILE': '3518',
+    'RICHMOND PLAINS': '3518',
+    'SKINNERS FLAT': '3518',
+    'WEDDERBURN JUNCTION': '3518',
+    'WEHLA': '3518',
+    'WOOLSHED FLAT': '3518',
+    'WOOSANG': '3518',
+    'KINYPANIAL': '3520',
+    'KORONG VALE': '3520',
+    'PYALONG': '3521',
+    'EMU FLAT': '3522',
+    'GLENHOPE EAST': '3522',
+    'TOOBORAC': '3522',
+    'COSTERFIELD': '3523',
+    'DERRINAL': '3523',
+    'HEATHCOTE SOUTH': '3523',
+    'KNOWSLEY': '3523',
+    'LADYS PASS': '3523',
+    'MOORMBOOL WEST': '3523',
+    'MOUNT CAMEL': '3523',
+    'REDCASTLE': '3523',
+    'BARRAKEE': '3525',
+    'BUCKRABANYULE': '3525',
+    'CHIRRIP': '3525',
+    'GRANITE FLAT': '3525',
+    'LAKE MARMAL': '3525',
+    'NAREEWILLOCK': '3525',
+    'TERRAPPEE': '3525',
+    'WOOROONOOK': '3525',
+    'WYCHITELLA': '3525',
+    'WYCHITELLA NORTH': '3525',
+    'YEUNGROON': '3525',
+    'YEUNGROON EAST': '3525',
+    'BUNGULUKE': '3527',
+    'DUMOSA': '3527',
+    'GLENLOTH': '3527',
+    'GLENLOTH EAST': '3527',
+    'JERUK': '3527',
+    'NINYEUNOOK': '3527',
+    'TEDDYWADDY': '3527',
+    'TEDDYWADDY WEST': '3527',
+    'THALIA': '3527',
+    'TOWANINNY': '3527',
+    'TOWANINNY SOUTH': '3527',
+    'WYCHEPROOF': '3527',
+    'WYCHEPROOF SOUTH': '3527',
+    'KALPIENUNG': '3529',
+    'NULLAWIL': '3529',
+    'CULGOA': '3530',
+    'WANGIE': '3530',
+    'WARNE': '3530',
+    'BERRIWILLOCK': '3531',
+    'BOIGBEAT': '3531',
+    'BIMBOURIE': '3533',
+    'LAKE TYRRELL': '3533',
+    'MITTYACK': '3533',
+    'MYALL': '3533',
+    'NANDALY': '3533',
+    'NINDA': '3533',
+    'NYARRIN': '3533',
+    'PIER MILAN': '3533',
+    'SEA LAKE': '3533',
+    'STRATEN': '3533',
+    'TYENNA': '3533',
+    'TYRRELL': '3533',
+    'TYRRELL DOWNS': '3533',
+    'BARRAPORT': '3537',
+    'BARRAPORT WEST': '3537',
+    'BOORT': '3537',
+    'CANARY ISLAND': '3537',
+    'CATUMNAL': '3537',
+    'GREDGWIN': '3537',
+    'LEAGHUR': '3537',
+    'MINMINDIE': '3537',
+    'YANDO': '3537',
+    'CANNIE': '3540',
+    'OAKVALE': '3540',
+    'QUAMBATOOK': '3540',
+    'COKUM': '3542',
+    'LALBERT': '3542',
+    'TITTYBONG': '3542',
+    'CHINANGIN': '3544',
+    'GOWANFORD': '3544',
+    'MURNUNGIN': '3544',
+    'ULTIMA': '3544',
+    'ULTIMA EAST': '3544',
+    'WAITCHIE': '3544',
+    'BOLTON': '3546',
+    'CHINKAPOOK': '3546',
+    'COCAMBA': '3546',
+    'GERAHMIN': '3546',
+    'MANANGATANG': '3546',
+    'TUROAR': '3546',
+    'WINNAMBOOL': '3546',
+    'ANNUELLO': '3549',
+    'BANNERTON': '3549',
+    'LIPAROO': '3549',
+    'ROBINVALE': '3549',
+    'ROBINVALE IRRIGATION DISTRICT SECTION B': '3549',
+    'ROBINVALE IRRIGATION DISTRICT SECTION C': '3549',
+    'ROBINVALE IRRIGATION DISTRICT SECTION D': '3549',
+    'ROBINVALE IRRIGATION DISTRICT SECTION E': '3549',
+    'TOL TOL': '3549',
+    'WANDOWN': '3549',
+    'WEMEN': '3549',
+    'BENDIGO': '3550',
+    'BENDIGO SOUTH': '3550',
+    'DIAMOND HILL': '3550',
+    'EAST BENDIGO': '3550',
+    'FLORA HILL': '3550',
+    'KENNINGTON': '3550',
+    'LONG GULLY': '3550',
+    'NORTH BENDIGO': '3550',
+    'QUARRY HILL': '3550',
+    'SANDHURST EAST': '3550',
+    'SPRING GULLY': '3550',
+    'STRATHDALE': '3550',
+    'TYSONS REEF': '3550',
+    'WEST BENDIGO': '3550',
+    'WHITE HILLS': '3550',
+    'ARNOLD WEST': '3551',
+    'AXE CREEK': '3551',
+    'AXEDALE': '3551',
+    'BAGSHOT': '3551',
+    'BAGSHOT NORTH': '3551',
+    'BENDIGO FORWARD': '3551',
+    'CORNELLA': '3551',
+    'EMU CREEK': '3551',
+    'EPPALOCK': '3551',
+    'EPSOM': '3551',
+    'HUNTLY': '3551',
+    'HUNTLY NORTH': '3551',
+    'JUNORTOUN': '3551',
+    'KIMBOLTON': '3551',
+    'LLANELLY': '3551',
+    'LOCKWOOD': '3551',
+    'LOCKWOOD SOUTH': '3551',
+    'LONGLEA': '3551',
+    'MAIDEN GULLY': '3551',
+    'MANDURANG': '3551',
+    'MANDURANG SOUTH': '3551',
+    'MURPHYS CREEK': '3551',
+    'MYOLA EAST': '3551',
+    'PAINSWICK': '3551',
+    'PILCHERS BRIDGE': '3551',
+    'SEDGWICK': '3551',
+    'STRATHFIELDSAYE': '3551',
+    'TARNAGULLA': '3551',
+    'TOOLLEEN': '3551',
+    'WAANYARRA': '3551',
+    'WELLSFORD': '3551',
+    'WOODSTOCK ON LODDON': '3551',
+    'GOLDEN GULLY': '3555',
+    'GOLDEN SQUARE': '3555',
+    'LANSELL PLAZA': '3555',
+    'CALIFORNIA GULLY': '3556',
+    'CAMPBELLS FOREST': '3556',
+    'EAGLEHAWK': '3556',
+    'EAGLEHAWK NORTH': '3556',
+    'JACKASS FLAT': '3556',
+    'MYERS FLAT': '3556',
+    'SAILORS GULLY': '3556',
+    'SEBASTIAN': '3556',
+    'WHIPSTICK': '3556',
+    'WOODVALE': '3556',
+    'BARNADOWN': '3557',
+    'FOSTERVILLE': '3557',
+    'GOORNONG': '3557',
+    'MUSKERRY': '3557',
+    'MUSKERRY EAST': '3557',
+    'BURNEWANG': '3558',
+    'COROP WEST': '3558',
+    'CREEK VIEW': '3558',
+    'ELMORE': '3558',
+    'HUNTER': '3558',
+    'AVONMORE': '3559',
+    'COLBINABBIN': '3559',
+    'COROP': '3559',
+    'RUNNYMEDE': '3559',
+    'BALLENDELLA': '3561',
+    'BAMAWM': '3561',
+    'BAMAWM EXTENSION': '3561',
+    'DIGGORA': '3561',
+    'DIGGORA WEST': '3561',
+    'FAIRY DELL': '3561',
+    'NANNEELLA': '3561',
+    'ROCHESTER': '3561',
+    'ROCHESTER WEST': '3561',
+    'THE SETTLEMENT': '3561',
+    'TIMMERING': '3561',
+    'TORRUMBARRY': '3562',
+    'LOCKINGTON': '3563',
+    'CAMPASPE WEST': '3564',
+    'ECHUCA': '3564',
+    'ECHUCA EAST': '3564',
+    'ECHUCA SOUTH': '3564',
+    'ECHUCA VILLAGE': '3564',
+    'ECHUCA WEST': '3564',
+    'KANYAPELLA': '3564',
+    'MCEVOYS': '3564',
+    'PATHO': '3564',
+    'PATHO WEST': '3564',
+    'ROSLYNMEAD': '3564',
+    'SIMMIE': '3564',
+    'WHARPARILLA': '3564',
+    'KOTTA': '3565',
+    'GUNBOWER': '3566',
+    'HORFIELD': '3567',
+    'LEITCHVILLE': '3567',
+    'BURKES BRIDGE': '3568',
+    'COHUNA': '3568',
+    'CULLEN': '3568',
+    'DALTONS BRIDGE': '3568',
+    'GANNAWARRA': '3568',
+    'KEELY': '3568',
+    'MACORNA NORTH': '3568',
+    'MCMILLANS': '3568',
+    'MEAD': '3568',
+    'MINCHA WEST': '3568',
+    'WEE WEE RUP': '3568',
+    'AUCHMORE': '3570',
+    'DRUMMARTIN': '3570',
+    'KAMAROOKA': '3570',
+    'NEILBOROUGH': '3570',
+    'RAYWOOD': '3570',
+    'SUMMERFIELD': '3570',
+    'DINGEE': '3571',
+    'KAMAROOKA NORTH': '3571',
+    'POMPAPIEL': '3571',
+    'TANDARRA': '3571',
+    'MILLOO': '3572',
+    'PIAVELLA': '3572',
+    'PRAIRIE': '3572',
+    'CALIVIL': '3573',
+    'MITIAMO': '3573',
+    'PINE GROVE EAST': '3573',
+    'TERRICK TERRICK EAST': '3573',
+    'GLADFIELD': '3575',
+    'JUNGABURRA': '3575',
+    'LODDON VALE': '3575',
+    'MINCHA': '3575',
+    'MOLOGA': '3575',
+    'PYRAMID HILL': '3575',
+    'SYLVATERRE': '3575',
+    'TERRICK TERRICK': '3575',
+    'YARRAWALLA': '3575',
+    'DURHAM OX': '3576',
+    'APPIN SOUTH': '3579',
+    'BAEL BAEL': '3579',
+    'BEAUCHAMP': '3579',
+    'BENJEROOP': '3579',
+    'BUDGERUM EAST': '3579',
+    'CAPELS CROSSING': '3579',
+    'DINGWALL': '3579',
+    'FAIRLEY': '3579',
+    'GONN CROSSING': '3579',
+    'KERANG': '3579',
+    'KERANG EAST': '3579',
+    'KOROOP': '3579',
+    'LAKE MERAN': '3579',
+    'MACORNA': '3579',
+    'MEERING WEST': '3579',
+    'MILNES BRIDGE': '3579',
+    'MURRABIT': '3579',
+    'MURRABIT WEST': '3579',
+    'MYSTIC PARK': '3579',
+    'NORMANVILLE': '3579',
+    'PINE VIEW': '3579',
+    'REEDY LAKE': '3579',
+    'SANDHILL LAKE': '3579',
+    'TEAL POINT': '3579',
+    'TRAGOWEL': '3579',
+    'WESTBY': '3579',
+    'KOONDROOK': '3580',
+    'LAKE CHARM': '3581',
+    'TRESCO': '3583',
+    'LAKE BOGA': '3584',
+    'TRESCO WEST': '3584',
+    'CASTLE DONNINGTON': '3585',
+    'CHILLINGOLLAH': '3585',
+    'FISH POINT': '3585',
+    'GOSCHEN': '3585',
+    'KUNAT': '3585',
+    'MEATIAN': '3585',
+    'MURRAY DOWNS': '3585',
+    'NOWIE': '3585',
+    'NYRRABY': '3585',
+    'PIRA': '3585',
+    'POLISBET': '3585',
+    'SWAN HILL': '3585',
+    'SWAN HILL PIONEER': '3585',
+    'SWAN HILL WEST': '3585',
+    'WINLATON': '3585',
+    'MALLAN': '3586',
+    'MURRAWEE': '3586',
+    'MURRAYDALE': '3586',
+    'PENTAL ISLAND': '3586',
+    'TYNTYNDER': '3586',
+    'TYNTYNDER SOUTH': '3586',
+    'WOORINEN SOUTH': '3588',
+    'WOORINEN': '3589',
+    'WOORINEN NORTH': '3589',
+    'BEVERFORD': '3590',
+    'VINIFERA': '3591',
+    'NYAH': '3594',
+    'NYAH WEST': '3595',
+    'MIRALIE': '3596',
+    'TOWAN': '3596',
+    'WOOD WOOD': '3596',
+    'KENLEY': '3597',
+    'KOOLOONONG': '3597',
+    'LAKE POWELL': '3597',
+    'NARRUNG': '3597',
+    'NATYA': '3597',
+    'PIANGIL': '3597',
+    'BOUNDARY BEND': '3599',
+    'TABILK': '3607',
+    'BAILIESTON': '3608',
+    'GOULBURN WEIR': '3608',
+    'GRAYTOWN': '3608',
+    'KIRWANS BRIDGE': '3608',
+    'MITCHELLSTOWN': '3608',
+    'NAGAMBIE': '3608',
+    'WAHRING': '3608',
+    'WIRRATE': '3608',
+    'DHURRINGILE': '3610',
+    'MOORILIM': '3610',
+    'MURCHISON': '3610',
+    'MURCHISON EAST': '3610',
+    'MURCHISON NORTH': '3610',
+    'MOORA': '3612',
+    'RUSHWORTH': '3612',
+    'WANALTA': '3612',
+    'WARANGA SHORES': '3612',
+    'WHROO': '3612',
+    'TOOLAMBA': '3614',
+    'TOOLAMBA WEST': '3614',
+    'GILLIESTON': '3616',
+    'GIRGARRE EAST': '3616',
+    'HARSTON': '3616',
+    'MOOROOPNA NORTH WEST': '3616',
+    'TATURA': '3616',
+    'TATURA EAST': '3616',
+    'WARANGA': '3616',
+    'BYRNESIDE': '3617',
+    'MERRIGUM': '3618',
+    'KYABRAM': '3620',
+    'KYABRAM SOUTH': '3620',
+    'LANCASTER': '3620',
+    'MOUNT SCOBIE': '3620',
+    'ST GERMAINS': '3620',
+    'TARIPTA': '3620',
+    'WYUNA': '3620',
+    'WYUNA EAST': '3620',
+    'KOYUGA SOUTH': '3621',
+    'KY WEST': '3621',
+    'KYVALLEY': '3621',
+    'TONGALA': '3621',
+    'YAMBUNA': '3621',
+    'CORNELIA CREEK': '3622',
+    'KOYUGA': '3622',
+    'STRATHALLAN': '3622',
+    'CARAG CARAG': '3623',
+    'STANHOPE SOUTH': '3623',
+    'GIRGARRE': '3624',
+    'ARDMONA': '3629',
+    'COOMBOONA': '3629',
+    'MOOROOPNA': '3629',
+    'MOOROOPNA NORTH': '3629',
+    'UNDERA': '3629',
+    'BRANDITT': '3630',
+    'CANIAMBO': '3630',
+    'COLLIVER': '3630',
+    'DUNKIRK': '3630',
+    'SHEPPARTON': '3630',
+    'SHEPPARTON SOUTH': '3630',
+    'ARCADIA SOUTH': '3631',
+    'COSGROVE': '3631',
+    'COSGROVE SOUTH': '3631',
+    'GRAHAMVALE': '3631',
+    'KARRAMOMUS': '3631',
+    'KIALLA EAST': '3631',
+    'KIALLA WEST': '3631',
+    'LEMNOS': '3631',
+    'ORRVALE': '3631',
+    'SHEPPARTON EAST': '3631',
+    'SHEPPARTON NORTH': '3631',
+    'CONGUPNA': '3633',
+    'BUNBARTHA': '3634',
+    'INVERGORDON SOUTH': '3634',
+    'KATANDRA': '3634',
+    'KATANDRA WEST': '3634',
+    'MARIONVALE': '3634',
+    'MARUNGI': '3634',
+    'TALLYGAROOPNA': '3634',
+    'ZEERUST': '3634',
+    'KAARIMBA': '3635',
+    'MUNDOONA': '3635',
+    'WUNGHNU': '3635',
+    'DRUMANURE': '3636',
+    'NARING': '3636',
+    'NUMURKAH': '3636',
+    'WAAIA': '3637',
+    'YALCA': '3637',
+    'KOTUPNA': '3638',
+    'NATHALIA': '3638',
+    'YIELIMA': '3638',
+    'BARMAH': '3639',
+    'LOWER MOIRA': '3639',
+    'PICOLA': '3639',
+    'PICOLA WEST': '3639',
+    'KATUNGA': '3640',
+    'BEARII': '3641',
+    'MYWEE': '3641',
+    'STRATHMERTON': '3641',
+    'ULUPNA': '3641',
+    'BAROOGA': '3644',
+    'COBRAM': '3644',
+    'COBRAM EAST': '3644',
+    'KOONOOMOO': '3644',
+    'LALALTY': '3644',
+    'MUCKATAH': '3644',
+    'YARROWEYAH': '3644',
+    'DOOKIE': '3646',
+    'MOUNT MAJOR': '3646',
+    'NALINGA': '3646',
+    'WAGGARANDALL': '3646',
+    'YABBA NORTH': '3646',
+    'YABBA SOUTH': '3646',
+    'YOUANMITE': '3646',
+    'KATAMATITE': '3649',
+    'KATAMATITE EAST': '3649',
+    'BROADFORD': '3658',
+    'CLONBINANE': '3658',
+    'FLOWERDALE': '3658',
+    'HAZELDENE': '3658',
+    'STRATH CREEK': '3658',
+    'SUGARLOAF CREEK': '3658',
+    'SUNDAY CREEK': '3658',
+    'TYAAK': '3658',
+    'WATERFORD PARK': '3658',
+    'TALLAROOK': '3659',
+    'CAVEAT': '3660',
+    'DROPMORE': '3660',
+    'DYSART': '3660',
+    'HIGHLANDS': '3660',
+    'HILLDENE': '3660',
+    'KERRISDALE': '3660',
+    'SEYMOUR': '3660',
+    'SEYMOUR SOUTH': '3660',
+    'TRAWOOL': '3660',
+    'WHITEHEADS CREEK': '3660',
+    'MANGALORE': '3663',
+    'AVENEL': '3664',
+    'UPTON HILL': '3664',
+    'LONGWOOD': '3665',
+    'BALMATTUM': '3666',
+    'CREIGHTON': '3666',
+    'CREIGHTONS CREEK': '3666',
+    'EUROA': '3666',
+    'GOORAM': '3666',
+    'KELVIN VIEW': '3666',
+    'KITHBROOK': '3666',
+    'LONGWOOD EAST': '3666',
+    'MIEPOLL': '3666',
+    'MOGLONEMBY': '3666',
+    'MOLKA': '3666',
+    'PRANJIP': '3666',
+    'RIGGS CREEK': '3666',
+    'RUFFY': '3666',
+    'SHEANS CREEK': '3666',
+    'STRATHBOGIE': '3666',
+    'TARCOMBE': '3666',
+    'BOHO': '3669',
+    'BOHO SOUTH': '3669',
+    'CREEK JUNCTION': '3669',
+    'EARLSTON': '3669',
+    'GOWANGARDIE': '3669',
+    'KOONDA': '3669',
+    'MARRAWEENEY': '3669',
+    'TAMLEUGH': '3669',
+    'TAMLEUGH NORTH': '3669',
+    'UPOTIPOTPON': '3669',
+    'VIOLET TOWN': '3669',
+    'BADDAGINNIE': '3670',
+    'TARNOOK': '3670',
+    'WARRENBAYNE': '3670',
+    'BENALLA': '3672',
+    'BENALLA WEST': '3672',
+    'BROKEN CREEK': '3673',
+    'GOOMALIBEE': '3673',
+    'LIMA': '3673',
+    'LIMA EAST': '3673',
+    'LIMA SOUTH': '3673',
+    'LURG': '3673',
+    'MOLYULLAH': '3673',
+    'MOORNGAG': '3673',
+    'SAMARIA': '3673',
+    'SWANPOOL': '3673',
+    'TATONG': '3673',
+    'UPPER LURG': '3673',
+    'UPPER RYANS CREEK': '3673',
+    'WINTON NORTH': '3673',
+    'BOWEYA': '3675',
+    'BOWEYA NORTH': '3675',
+    'GLENROWAN': '3675',
+    'GLENROWAN WEST': '3675',
+    'GRETA SOUTH': '3675',
+    'GRETA WEST': '3675',
+    'HANSONVILLE': '3675',
+    'MOUNT BRUNO': '3675',
+    'TAMINICK': '3675',
+    'APPIN PARK': '3677',
+    'WANGARATTA': '3677',
+    'WANGARATTA WEST': '3677',
+    'BOBINAWARRAH': '3678',
+    'BOORHAMAN': '3678',
+    'BOORHAMAN EAST': '3678',
+    'BOWSER': '3678',
+    'BYAWATHA': '3678',
+    'CARBOOR': '3678',
+    'CHESHUNT': '3678',
+    'CHESHUNT SOUTH': '3678',
+    'DOCKER': '3678',
+    'DOCKERS PLAINS': '3678',
+    'EAST WANGARATTA': '3678',
+    'EDI': '3678',
+    'EDI UPPER': '3678',
+    'EVERTON': '3678',
+    'EVERTON UPPER': '3678',
+    'KING VALLEY': '3678',
+    'LACEBY': '3678',
+    'LONDRIGAN': '3678',
+    'MARKWOOD': '3678',
+    'MEADOW CREEK': '3678',
+    'MILAWA': '3678',
+    'NORTH WANGARATTA': '3678',
+    'OXLEY FLATS': '3678',
+    'PEECHELBA': '3678',
+    'PEECHELBA EAST': '3678',
+    'ROSE RIVER': '3678',
+    'TARRAWINGEE': '3678',
+    'WABONGA': '3678',
+    'WALDARA': '3678',
+    'WANGANDARY': '3678',
+    'WANGARATTA FORWARD': '3678',
+    'WANGARATTA SOUTH': '3678',
+    'WHITLANDS': '3678',
+    'BORALMA': '3682',
+    'LILLIPUT': '3682',
+    'NORONG': '3682',
+    'SPRINGHURST': '3682',
+    'CHILTERN': '3683',
+    'CHILTERN VALLEY': '3683',
+    'CORNISHTOWN': '3683',
+    'BOORHAMAN NORTH': '3685',
+    'BRIMIN': '3685',
+    'BROWNS PLAINS': '3685',
+    'CARLYLE': '3685',
+    'GOORAMADDA': '3685',
+    'GREAT SOUTHERN': '3685',
+    'LAKE MOODEMERE': '3685',
+    'NORONG CENTRAL': '3685',
+    'PRENTICE NORTH': '3685',
+    'RUTHERGLEN': '3685',
+    'WAHGUNYAH': '3687',
+    'BARNAWARTHA': '3688',
+    'INDIGO VALLEY': '3688',
+    'WEST WODONGA': '3690',
+    'WODONGA': '3690',
+    'WODONGA PLAZA': '3690',
+    'ALLANS FLAT': '3691',
+    'BARANDUDA': '3691',
+    'BARNAWARTHA NORTH': '3691',
+    'BELLBRIDGE': '3691',
+    'BERRINGAMA': '3691',
+    'BETHANGA': '3691',
+    'BONEGILLA': '3691',
+    'BUNGIL': '3691',
+    'CASTLE CREEK': '3691',
+    'CORAL BANK': '3691',
+    'DEDERANG': '3691',
+    'EBDEN': '3691',
+    'GATEWAY ISLAND': '3691',
+    'GLEN CREEK': '3691',
+    'GUNDOWRING': '3691',
+    'HUON CREEK': '3691',
+    'KANCOONA': '3691',
+    'KERGUNYAH': '3691',
+    'KERGUNYAH SOUTH': '3691',
+    'KIEWA': '3691',
+    'LENEVA': '3691',
+    'LUCYVALE': '3691',
+    'MONGANS BRIDGE': '3691',
+    'OSBORNES FLAT': '3691',
+    'RUNNING CREEK': '3691',
+    'STAGHORN FLAT': '3691',
+    'TALGARNO': '3691',
+    'TANGAMBALANGA': '3691',
+    'THOLOGOLONG': '3691',
+    'UPPER GUNDOWRING': '3691',
+    'WODONGA FORWARD': '3691',
+    'BANDIANA': '3694',
+    'BANDIANA MILPO': '3694',
+    'CHARLEROI': '3695',
+    'HUON': '3695',
+    'TAWONGA': '3697',
+    'TAWONGA SOUTH': '3698',
+    'BOGONG': '3699',
+    'MOUNT BEAUTY': '3699',
+    'BULLIOH': '3700',
+    'JARVIS CREEK': '3700',
+    'TALLANGATTA': '3700',
+    'TALLANGATTA EAST': '3700',
+    'DARTMOUTH': '3701',
+    'ESKDALE': '3701',
+    'GRANYA': '3701',
+    'MITTA MITTA': '3701',
+    'OLD TALLANGATTA': '3701',
+    'SHELLEY': '3701',
+    'TALLANDOON': '3701',
+    'TALLANGATTA SOUTH': '3701',
+    'TALLANGATTA VALLEY': '3701',
+    'KOETONG': '3704',
+    'CUDGEWA': '3705',
+    'BIGGARA': '3707',
+    'BRINGENBRONG': '3707',
+    'COLAC COLAC': '3707',
+    'CORRYONG': '3707',
+    'NARIEL VALLEY': '3707',
+    'THOWGLA VALLEY': '3707',
+    'TOM GROGGIN': '3707',
+    'TOWONG': '3707',
+    'TOWONG UPPER': '3707',
+    'TINTALDRA': '3708',
+    'BURROWYE': '3709',
+    'GUYS FOREST': '3709',
+    'MOUNT ALFRED': '3709',
+    'PINE MOUNTAIN': '3709',
+    'WALWA': '3709',
+    'RUBICON': '3712',
+    'EILDON': '3713',
+    'LAKE EILDON': '3713',
+    'TAYLOR BAY': '3713',
+    'ACHERON': '3714',
+    'ALEXANDRA': '3714',
+    'CATHKIN': '3714',
+    'DEVILS RIVER': '3714',
+    'FAWCETT': '3714',
+    'KORIELLA': '3714',
+    'MAINTONGOON': '3714',
+    'TAGGERTY': '3714',
+    'WHANREGARWEN': '3714',
+    'ANCONA': '3715',
+    'MERTON': '3715',
+    'WOODFIELD': '3715',
+    'GHIN GHIN': '3717',
+    'GLENBURN': '3717',
+    'HOMEWOOD': '3717',
+    'MURRINDINDI': '3717',
+    'YEA': '3717',
+    'MOLESWORTH': '3718',
+    'GOBUR': '3719',
+    'KANUMBRA': '3719',
+    'TERIP TERIP': '3719',
+    'YARCK': '3719',
+    'BONNIE DOON': '3720',
+    'BARWITE': '3722',
+    'MANSFIELD': '3722',
+    'MIRIMBAH': '3722',
+    'ARCHERTON': '3723',
+    'BARJARG': '3723',
+    'BOOROLITE': '3723',
+    'BRIDGE CREEK': '3723',
+    'DELATITE': '3723',
+    'GAFFNEYS CREEK': '3723',
+    'GOUGHS BAY': '3723',
+    'HOWES CREEK': '3723',
+    'HOWQUA': '3723',
+    'HOWQUA HILLS': '3723',
+    'HOWQUA INLET': '3723',
+    'JAMIESON': '3723',
+    'KEVINGTON': '3723',
+    'MACS COVE': '3723',
+    'MAINDAMPLE': '3723',
+    'MATLOCK': '3723',
+    'MERRIJIG': '3723',
+    'MOUNT BULLER': '3723',
+    'MOUNTAIN BAY': '3723',
+    'PIRIES': '3723',
+    'SAWMILL SETTLEMENT': '3723',
+    'TOLMIE': '3723',
+    'WOODS POINT': '3723',
+    'BOXWOOD': '3725',
+    'CHESNEY VALE': '3725',
+    'GOORAMBAT': '3725',
+    'MAJOR PLAINS': '3725',
+    'STEWARTON': '3725',
+    'BUNGEET': '3726',
+    'BUNGEET WEST': '3726',
+    'DEVENISH': '3726',
+    'THOONA': '3726',
+    'ALMONDS': '3727',
+    'LAKE ROWAN': '3727',
+    'PELLUEBLA': '3727',
+    'SAINT JAMES': '3727',
+    'YUNDOOL': '3727',
+    'BOOMAHNOOMOONAH': '3728',
+    'TUNGAMAH': '3728',
+    'WILBY': '3728',
+    'YOUARANG': '3728',
+    'BATHUMI': '3730',
+    'BOOSEY': '3730',
+    'BUNDALONG': '3730',
+    'BUNDALONG SOUTH': '3730',
+    'BURRAMINE': '3730',
+    'BURRAMINE SOUTH': '3730',
+    'ESMOND': '3730',
+    'TELFORD': '3730',
+    'YARRAWONGA SOUTH': '3730',
+    'MOYHU': '3732',
+    'MYRRHEE': '3732',
+    'WHITFIELD': '3733',
+    'BOWMANS FOREST': '3735',
+    'WHOROULY': '3735',
+    'WHOROULY EAST': '3735',
+    'WHOROULY SOUTH': '3735',
+    'ABBEYARD': '3737',
+    'BARWIDGEE': '3737',
+    'BUFFALO RIVER': '3737',
+    'DANDONGADALE': '3737',
+    'GAPSTED': '3737',
+    'MERRIANG': '3737',
+    'MERRIANG SOUTH': '3737',
+    'MUDGEGONGA': '3737',
+    'MYRTLEFORD': '3737',
+    'NUG NUG': '3737',
+    'ROSEWHITE': '3737',
+    'SELWYN': '3737',
+    'WONNANGATTA': '3737',
+    'OVENS': '3738',
+    'EUROBIN': '3739',
+    'BUCKLAND': '3740',
+    'MOUNT BUFFALO': '3740',
+    'POREPUNKAH': '3740',
+    'BRIGHT': '3741',
+    'FREEBURGH': '3741',
+    'GERMANTOWN': '3741',
+    'HARRIETVILLE': '3741',
+    'HOTHAM HEIGHTS': '3741',
+    'MOUNT HOTHAM': '3741',
+    'SMOKO': '3741',
+    'WANDILIGONG': '3744',
+    'ELDORADO': '3746',
+    'BEECHWORTH': '3747',
+    'MURMUNGEE': '3747',
+    'STANLEY': '3747',
+    'WOOLSHED': '3747',
+    'WOORAGEE': '3747',
+    'BRUARONG': '3749',
+    'YACKANDANDAH': '3749',
+    'WOLLERT': '3750',
+    'MORANG SOUTH': '3752',
+    'SOUTH MORANG': '3752',
+    'BEVERIDGE': '3753',
+    'DOREEN': '3754',
+    'MERNDA': '3754',
+    'YAN YEAN': '3755',
+    'CHINTIN': '3756',
+    'DARRAWEIT GUIM': '3756',
+    'HIDDEN VALLEY': '3756',
+    'UPPER PLENTY': '3756',
+    'WALLAN': '3756',
+    'BRUCES CREEK': '3757',
+    'EDEN PARK': '3757',
+    'HUMEVALE': '3757',
+    'KINGLAKE CENTRAL': '3757',
+    'KINGLAKE WEST': '3757',
+    'PHEASANT CREEK': '3757',
+    'WHITTLESEA': '3757',
+    'HEATHCOTE JUNCTION': '3758',
+    'WANDONG': '3758',
+    'PANTON HILL': '3759',
+    'SMITHS GULLY': '3760',
+    'BYLANDS': '3762',
+    'KINGLAKE': '3763',
+    'MOUNT SLIDE': '3763',
+    'GLENAROUA': '3764',
+    'HIGH CAMP': '3764',
+    'KILMORE': '3764',
+    'KILMORE EAST': '3764',
+    'MORANDING': '3764',
+    'TANTARABOO': '3764',
+    'WILLOWMAVIN': '3764',
+    'MONTROSE': '3765',
+    'KALORAMA': '3766',
+    'MOUNT DANDENONG': '3767',
+    'GRUYERE': '3770',
+    'YERING': '3770',
+    'CHRISTMAS HILLS': '3775',
+    'DIXONS CREEK': '3775',
+    'STEELS CREEK': '3775',
+    'TARRAWARRA': '3775',
+    'YARRA GLEN': '3775',
+    'BADGER CREEK': '3777',
+    'CASTELLA': '3777',
+    'CHUM CREEK': '3777',
+    'HEALESVILLE': '3777',
+    'HEALESVILLE MAIN STREET': '3777',
+    'HEALESVILLE POST SHOP': '3777',
+    'MOUNT TOOLEBEWONG': '3777',
+    'TOOLANGI': '3777',
+    'FERNSHAW': '3778',
+    'NARBETHONG': '3778',
+    'CAMBARVILLE': '3779',
+    'MARYSVILLE': '3779',
+    'COCKATOO': '3781',
+    'MOUNT BURNETT': '3781',
+    'NANGANA': '3781',
+    'AVONSLEIGH': '3782',
+    'CLEMATIS': '3782',
+    'EMERALD': '3782',
+    'MACCLESFIELD': '3782',
+    'GEMBROOK': '3783',
+    'TREMONT': '3785',
+    'FERNY CREEK': '3786',
+    'SASSAFRAS GULLY': '3787',
+    'SHERBROOKE': '3789',
+    'KALLISTA': '3791',
+    'THE PATCH': '3792',
+    'MONBULK': '3793',
+    'SILVAN': '3795',
+    'MOUNT EVELYN': '3796',
+    'GILDEROY': '3797',
+    'GLADYSDALE': '3797',
+    'POWELLTOWN': '3797',
+    'THREE BRIDGES': '3797',
+    'YARRA JUNCTION': '3797',
+    'BIG PATS CREEK': '3799',
+    'EAST WARBURTON': '3799',
+    'MCMAHONS CREEK': '3799',
+    'MILLGROVE': '3799',
+    'WARBURTON': '3799',
+    'WESBURN': '3799',
+    'MONASH UNIVERSITY': '3800',
+    'ENDEAVOUR HILLS': '3802',
+    'HALLAM': '3803',
+    'NARRE WARREN EAST': '3804',
+    'NARRE WARREN NORTH': '3804',
+    'FOUNTAIN GATE': '3805',
+    'NARRE WARREN': '3805',
+    'NARRE WARREN SOUTH': '3805',
+    'BERWICK': '3806',
+    'HARKAWAY': '3806',
+    'GUYS HILL': '3807',
+    'BEACONSFIELD UPPER': '3808',
+    'DEWHURST': '3808',
+    'OFFICER': '3809',
+    'OFFICER SOUTH': '3809',
+    'PAKENHAM': '3810',
+    'PAKENHAM SOUTH': '3810',
+    'PAKENHAM UPPER': '3810',
+    'RYTHDALE': '3810',
+    'MARYKNOLL': '3812',
+    'NAR NAR GOON': '3812',
+    'NAR NAR GOON NORTH': '3812',
+    'TYNONG': '3813',
+    'TYNONG NORTH': '3813',
+    'CORA LYNN': '3814',
+    'GARFIELD': '3814',
+    'GARFIELD NORTH': '3814',
+    'VERVALE': '3814',
+    'BUNYIP': '3815',
+    'BUNYIP NORTH': '3815',
+    'IONA': '3815',
+    'TONIMBUK': '3815',
+    'LABERTOUCHE': '3816',
+    'LONGWARRY': '3816',
+    'LONGWARRY NORTH': '3816',
+    'MODELLA': '3816',
+    'ATHLONE': '3818',
+    'DROUIN': '3818',
+    'DROUIN EAST': '3818',
+    'DROUIN SOUTH': '3818',
+    'DROUIN WEST': '3818',
+    'HALLORA': '3818',
+    'JINDIVICK': '3818',
+    'RIPPLEBROOK': '3818',
+    'LILLICO': '3820',
+    'WARRAGUL': '3820',
+    'BRANDY CREEK': '3821',
+    'BRAVINGTON': '3821',
+    'BULN BULN': '3821',
+    'BULN BULN EAST': '3821',
+    'CROSSOVER': '3821',
+    'ELLINBANK': '3821',
+    'FERNDALE': '3821',
+    'LARDNER': '3821',
+    'NILMA': '3821',
+    'NILMA NORTH': '3821',
+    'ROKEBY': '3821',
+    'SEAVIEW': '3821',
+    'SHADY CREEK': '3821',
+    'TETOORA ROAD': '3821',
+    'WARRAGUL SOUTH': '3821',
+    'WARRAGUL WEST': '3821',
+    'CLOVERLEA': '3822',
+    'DARNUM': '3822',
+    'GAINSBOROUGH': '3822',
+    'ALLAMBEE': '3823',
+    'YARRAGON': '3823',
+    'YARRAGON SOUTH': '3823',
+    'CHILDERS': '3824',
+    'NARRACAN': '3824',
+    'THORPDALE SOUTH': '3824',
+    'TRAFALGAR': '3824',
+    'TRAFALGAR EAST': '3824',
+    'TRAFALGAR SOUTH': '3824',
+    'ABERFELDY': '3825',
+    'AMOR': '3825',
+    'BOOLA': '3825',
+    'CARINGAL': '3825',
+    'COALVILLE': '3825',
+    'COOPERS CREEK': '3825',
+    'ERICA': '3825',
+    'FUMINA': '3825',
+    'FUMINA SOUTH': '3825',
+    'HERNES OAK': '3825',
+    'JACOB CREEK': '3825',
+    'JERICHO': '3825',
+    'MOE': '3825',
+    'MOE SOUTH': '3825',
+    'MOONDARRA': '3825',
+    'NEWBOROUGH': '3825',
+    'NEWBOROUGH EAST': '3825',
+    'RAWSON': '3825',
+    'TANJIL': '3825',
+    'TANJIL SOUTH': '3825',
+    'THALLOO': '3825',
+    'TOOMBON': '3825',
+    'WALHALLA': '3825',
+    'WALHALLA EAST': '3825',
+    'WESTBURY': '3825',
+    'WILLOW GROVE': '3825',
+    'YALLOURN': '3825',
+    'YALLOURN NORTH': '3825',
+    'NEERIM': '3831',
+    'NEERIM EAST': '3831',
+    'NEERIM SOUTH': '3831',
+    'NAYOOK': '3832',
+    'NEERIM JUNCTION': '3832',
+    'NEERIM NORTH': '3832',
+    'ADA': '3833',
+    'BAW BAW VILLAGE': '3833',
+    'GENTLE ANNIE': '3833',
+    'ICY CREEK': '3833',
+    'LOCH VALLEY': '3833',
+    'NOOJEE': '3833',
+    'PIEDMONT': '3833',
+    'TANJIL BREN': '3833',
+    'TOORONGO': '3833',
+    'VESPER': '3833',
+    'THORPDALE': '3835',
+    'DRIFFIELD': '3840',
+    'HAZELWOOD': '3840',
+    'HAZELWOOD NORTH': '3840',
+    'HAZELWOOD SOUTH': '3840',
+    'JEERALANG': '3840',
+    'JEERALANG JUNCTION': '3840',
+    'MID VALLEY': '3840',
+    'MORWELL': '3840',
+    'MORWELL EAST': '3840',
+    'MORWELL UPPER': '3840',
+    'CHURCHILL': '3842',
+    'BLACKWARRY': '3844',
+    'CALLIGNEE': '3844',
+    'CALLIGNEE NORTH': '3844',
+    'CALLIGNEE SOUTH': '3844',
+    'CARRAJUNG': '3844',
+    'CARRAJUNG LOWER': '3844',
+    'CARRAJUNG SOUTH': '3844',
+    'FLYNNS CREEK': '3844',
+    'KOORNALLA': '3844',
+    'LOY YANG': '3844',
+    'MOUNT TASSIE': '3844',
+    'TRARALGON': '3844',
+    'TRARALGON EAST': '3844',
+    'TRARALGON SOUTH': '3844',
+    'TYERS': '3844',
+    'HIAMDALE': '3847',
+    'NAMBROK': '3847',
+    'WILLUNG': '3847',
+    'WILLUNG SOUTH': '3847',
+    'SALE': '3850',
+    'SALE NORTH': '3850',
+    'WURRUK': '3850',
+    'AIRLY': '3851',
+    'BUNDALAGUAH': '3851',
+    'CLYDEBANK': '3851',
+    'COBAINS': '3851',
+    'DARRIMAN': '3851',
+    'DUTSON': '3851',
+    'DUTSON DOWNS': '3851',
+    'FLAMINGO BEACH': '3851',
+    'FULHAM': '3851',
+    'GIFFARD': '3851',
+    'GIFFARD WEST': '3851',
+    'GLOMAR BEACH': '3851',
+    'GOLDEN BEACH': '3851',
+    'KILMANY': '3851',
+    'LAKE WELLINGTON': '3851',
+    'LOCH SPORT': '3851',
+    'LONGFORD': '3851',
+    'MONTGOMERY': '3851',
+    'MYRTLEBANK': '3851',
+    'PEARSONDALE': '3851',
+    'SEACOMBE': '3851',
+    'SEASPRAY': '3851',
+    'SOMERTON PARK': '3851',
+    'STRADBROKE': '3851',
+    'THE HEART': '3851',
+    'THE HONEYSUCKLES': '3851',
+    'EAST SALE': '3852',
+    'SALE EAST RAAF': '3852',
+    'GLENGARRY': '3854',
+    'GLENGARRY NORTH': '3854',
+    'GLENGARRY WEST': '3854',
+    'COWWARR': '3857',
+    'ARBUCKLE': '3858',
+    'BILLABONG': '3858',
+    'BURAGWONDUC': '3858',
+    'CROOKAYAN': '3858',
+    'DAWSON': '3858',
+    'DENISON': '3858',
+    'GILLUM': '3858',
+    'GLEN FALLOCH': '3858',
+    'GLENMAGGIE': '3858',
+    'HEYFIELD': '3858',
+    'HOWITT PLAINS': '3858',
+    'LICOLA': '3858',
+    'LICOLA NORTH': '3858',
+    'REYNARD': '3858',
+    'SARGOOD': '3858',
+    'SEATON': '3858',
+    'TAMBORITHA': '3858',
+    'WINNINDOO': '3858',
+    'WORROWING': '3858',
+    'YANGOURA': '3858',
+    'MAFFRA WEST UPPER': '3859',
+    'TINAMBA': '3859',
+    'TINAMBA WEST': '3859',
+    'BOISDALE': '3860',
+    'BRIAGOLONG': '3860',
+    'BUSHY PARK': '3860',
+    'COONGULLA': '3860',
+    'KOOROOL': '3860',
+    'MONOMAK': '3860',
+    'MOROKA': '3860',
+    'NAP NAP MARRA': '3860',
+    'RIVERSLEA': '3860',
+    'TOOLOME': '3860',
+    'VALENCIA CREEK': '3860',
+    'WOOLENOOK': '3860',
+    'WRATHUNG': '3860',
+    'WRIXON': '3860',
+    'CASTLEBURN': '3862',
+    'COBBANNAH': '3862',
+    'COWA': '3862',
+    'CROOKED RIVER': '3862',
+    'DARGO': '3862',
+    'HAWKHURST': '3862',
+    'HOLLANDS LANDING': '3862',
+    'LLOWALONG': '3862',
+    'MEERLIEU': '3862',
+    'MIOWERA': '3862',
+    'MOORNAPA': '3862',
+    'MUNRO': '3862',
+    'PERRY BRIDGE': '3862',
+    'STOCKDALE': '3862',
+    'WATERFORD': '3862',
+    'WONGUNGARRA': '3862',
+    'FERNBANK': '3864',
+    'GLENALADALE': '3864',
+    'THE FINGERBOARD': '3864',
+    'LINDENOW': '3865',
+    'JUMBUK': '3869',
+    'YINNAR': '3869',
+    'YINNAR SOUTH': '3869',
+    'BOOLARRA': '3870',
+    'BOOLARRA SOUTH': '3870',
+    'BUDGEREE': '3870',
+    'BUDGEREE EAST': '3870',
+    'GRAND RIDGE': '3870',
+    'JOHNSTONES HILL': '3870',
+    'MIRBOO EAST': '3870',
+    'ALLAMBEE RESERVE': '3871',
+    'ALLAMBEE SOUTH': '3871',
+    'BAROMI': '3871',
+    'DARLIMURLA': '3871',
+    'DELBURN': '3871',
+    'DOLLAR': '3871',
+    'MIRBOO': '3871',
+    'MIRBOO NORTH': '3871',
+    'GORMANDALE': '3873',
+    'CHERRILONG': '3874',
+    'MCLOUGHLINS BEACH': '3874',
+    'WOODSIDE BEACH': '3874',
+    'WOODSIDE NORTH': '3874',
+    'BAIRNSDALE': '3875',
+    'BENGWORDEN': '3875',
+    'BROADLANDS': '3875',
+    'BULLUMWAAL': '3875',
+    'CALULU': '3875',
+    'CLIFTON CREEK': '3875',
+    'DEPTFORD': '3875',
+    'EAST BAIRNSDALE': '3875',
+    'ELLASWOOD': '3875',
+    'FLAGGY CREEK': '3875',
+    'FORGE CREEK': '3875',
+    'GOON NURE': '3875',
+    'GRANITE ROCK': '3875',
+    'IGUANA CREEK': '3875',
+    'LINDENOW SOUTH': '3875',
+    'MELWOOD': '3875',
+    'MOUNT TAYLOR': '3875',
+    'NEWLANDS ARM': '3875',
+    'SARSFIELD': '3875',
+    'TABBERABBERA': '3875',
+    'WALPA': '3875',
+    'WATERHOLES': '3875',
+    'WOODGLEN': '3875',
+    'WUK WUK': '3875',
+    'WY YUNG': '3875',
+    'EAGLE POINT': '3878',
+    'BOOLE POOLE': '3880',
+    'OCEAN GRANGE': '3880',
+    'PAYNESVILLE': '3880',
+    'RAYMOND ISLAND': '3880',
+    'BRUTHEN': '3885',
+    'BUCHAN': '3885',
+    'BUCHAN SOUTH': '3885',
+    'BUTCHERS RIDGE': '3885',
+    'GELANTIPY': '3885',
+    'MOSSIFACE': '3885',
+    'MURRINDAL': '3885',
+    'SUGGAN BUGGAN': '3885',
+    'TAMBO UPPER': '3885',
+    'W TREE': '3885',
+    'WISELEIGH': '3885',
+    'WULGULMERANG': '3885',
+    'WULGULMERANG EAST': '3885',
+    'WULGULMERANG WEST': '3885',
+    'YALMY': '3885',
+    'NEWMERELLA': '3886',
+    'LAKE TYERS': '3887',
+    'NOWA NOWA': '3887',
+    'WAIREWA': '3887',
+    'BENDOC': '3888',
+    'BETE BOLONG': '3888',
+    'BETE BOLONG NORTH': '3888',
+    'BONANG': '3888',
+    'BRODRIBB RIVER': '3888',
+    'CABANANDRA': '3888',
+    'CAPE CONRAN': '3888',
+    'CORRINGLE': '3888',
+    'DEDDICK VALLEY': '3888',
+    'GOONGERAH': '3888',
+    'HAYDENS BOG': '3888',
+    'JARRAHMOND': '3888',
+    'MARLO': '3888',
+    'NURRAN': '3888',
+    'OMEO VALLEY': '3888',
+    'ORBOST': '3888',
+    'SIMPSONS CREEK': '3888',
+    'TOSTAREE': '3888',
+    'TUBBUT': '3888',
+    'WAYGARA': '3888',
+    'BELLBIRD CREEK': '3889',
+    'BEMM RIVER': '3889',
+    'CABBAGE TREE CREEK': '3889',
+    'CLUB TERRACE': '3889',
+    'COMBIENBAR': '3889',
+    'ERRINUNDRA': '3889',
+    'MANORINA': '3889',
+    'BULDAH': '3890',
+    'CANN RIVER': '3890',
+    'CHANDLERS CREEK': '3890',
+    'NOORINBEE': '3890',
+    'NOORINBEE NORTH': '3890',
+    'TAMBOON': '3890',
+    'TONGHI CREEK': '3890',
+    'WEERAGUA': '3890',
+    'GENOA': '3891',
+    'GIPSY POINT': '3891',
+    'MARAMINGO CREEK': '3891',
+    'WALLAGARAUGH': '3891',
+    'WANGARABELL': '3891',
+    'WINGAN RIVER': '3891',
+    'WROXHAM': '3891',
+    'MALLACOOTA': '3892',
+    'DOUBLE BRIDGES': '3893',
+    'TAMBO CROSSING': '3893',
+    'DOCTORS FLAT': '3895',
+    'ENSAY': '3895',
+    'ENSAY NORTH': '3895',
+    'BINDI': '3896',
+    'BROOKVILLE': '3896',
+    'NUNNIONG': '3896',
+    'SWIFTS CREEK': '3896',
+    'TONGIO': '3896',
+    'ANGLERS REST': '3898',
+    'BINGO': '3898',
+    'BINGO MUNJIE': '3898',
+    'BUNDARA': '3898',
+    'COBUNGRA': '3898',
+    'DINNER PLAIN': '3898',
+    'GLEN VALLEY': '3898',
+    'GLEN WILLS': '3898',
+    'HINNOMUNJIE': '3898',
+    'OMEO': '3898',
+    'SHANNONVALE': '3898',
+    'BENAMBRA': '3900',
+    'COBBERAS': '3900',
+    'BUMBERRAH': '3902',
+    'JOHNSONVILLE': '3902',
+    'SWAN REACH': '3903',
+    'METUNG': '3904',
+    'KALIMNA': '3909',
+    'KALIMNA WEST': '3909',
+    'LAKE BUNGA': '3909',
+    'LAKE TYERS BEACH': '3909',
+    'LAKES ENTRANCE': '3909',
+    'NUNGURNER': '3909',
+    'NYERIMILANG': '3909',
+    'TOORLOO ARM': '3909',
+    'LANGWARRIN': '3910',
+    'BAXTER': '3911',
+    'LANGWARRIN SOUTH': '3911',
+    'PEARCEDALE': '3912',
+    'SOMERVILLE': '3912',
+    'TYABB': '3913',
+    'HASTINGS': '3915',
+    'TUERONG': '3915',
+    'MERRICKS': '3916',
+    'POINT LEO': '3916',
+    'SHOREHAM': '3916',
+    'BITTERN': '3918',
+    'CRIB POINT': '3919',
+    'HMAS CERBERUS': '3920',
+    'FRENCH ISLAND': '3921',
+    'TANKERTON': '3921',
+    'COWES': '3922',
+    'SILVERLEAVES': '3922',
+    'SMITHS BEACH': '3922',
+    'SUMMERLANDS': '3922',
+    'SUNDERLAND BAY': '3922',
+    'SUNSET STRIP': '3922',
+    'VENTNOR': '3922',
+    'WIMBLEDON HEIGHTS': '3922',
+    'RHYLL': '3923',
+    'CAPE WOOLAMAI': '3925',
+    'CHURCHILL ISLAND': '3925',
+    'NEWHAVEN': '3925',
+    'BALNARRING': '3926',
+    'BALNARRING BEACH': '3926',
+    'MERRICKS BEACH': '3926',
+    'MERRICKS NORTH': '3926',
+    'SOMERS': '3927',
+    'MAIN RIDGE': '3928',
+    'KUNYUNG': '3930',
+    'MOUNT ELIZA': '3930',
+    'MORNINGTON': '3931',
+    'MOOROODUC': '3933',
+    'MOUNT MARTHA': '3934',
+    'ARTHURS SEAT': '3936',
+    'DROMANA': '3936',
+    'RED HILL SOUTH': '3937',
+    'MCCRAE': '3938',
+    'BONEO': '3939',
+    'CAPE SCHANCK': '3939',
+    'FINGAL': '3939',
+    'ROSEBUD': '3939',
+    'ROSEBUD PLAZA': '3939',
+    'ROSEBUD WEST': '3940',
+    'RYE': '3941',
+    'ST ANDREWS BEACH': '3941',
+    'TOOTGAROOK': '3941',
+    'BLAIRGOWRIE': '3942',
+    'SORRENTO': '3943',
+    'PORTSEA': '3944',
+    'BELLVIEW': '3945',
+    'JEETHO': '3945',
+    'KROWERA': '3945',
+    'LOCH': '3945',
+    'WOODLEIGH': '3945',
+    'BENA': '3946',
+    'KARDELLA SOUTH': '3950',
+    'KORUMBURRA': '3950',
+    'KORUMBURRA SOUTH': '3950',
+    'STRZELECKI': '3950',
+    'WHITELAW': '3950',
+    'ARAWATA': '3951',
+    'FAIRBANK': '3951',
+    'JUMBUNNA': '3951',
+    'KARDELLA': '3951',
+    'KONGWAK': '3951',
+    'MOYARRA': '3951',
+    'OUTTRIM': '3951',
+    'RANCEBY': '3951',
+    'BERRYS CREEK': '3953',
+    'BOOROOL': '3953',
+    'HALLSTON': '3953',
+    'KOOROOMAN': '3953',
+    'LEONGATHA': '3953',
+    'LEONGATHA NORTH': '3953',
+    'LEONGATHA SOUTH': '3953',
+    'MARDAN': '3953',
+    'MOUNT ECCLES': '3953',
+    'MOUNT ECCLES SOUTH': '3953',
+    'NERRENA': '3953',
+    'RUBY': '3953',
+    'WILD DOG VALLEY': '3953',
+    'KOONWARRA': '3954',
+    'DUMBALK': '3956',
+    'DUMBALK NORTH': '3956',
+    'MEENIYAN': '3956',
+    'TARWIN': '3956',
+    'TARWIN LOWER': '3956',
+    'VENUS BAY': '3956',
+    'WALKERVILLE': '3956',
+    'WALKERVILLE SOUTH': '3956',
+    'BUFFALO': '3958',
+    'FISH CREEK': '3959',
+    'HODDLE': '3959',
+    'WARATAH BAY': '3959',
+    'BENNISON': '3960',
+    'BOOLARONG': '3960',
+    'FOSTER': '3960',
+    'FOSTER NORTH': '3960',
+    'GUNYAH': '3960',
+    'MOUNT BEST': '3960',
+    'SHALLOW INLET': '3960',
+    'TIDAL RIVER': '3960',
+    'TURTONS CREEK': '3960',
+    'WILSONS PROMONTORY': '3960',
+    'WONGA': '3960',
+    'WOORARRA WEST': '3960',
+    'YANAKIE': '3960',
+    'AGNES': '3962',
+    'CHRISTIES': '3962',
+    'TOORA': '3962',
+    'TOORA NORTH': '3962',
+    'WONYIP': '3962',
+    'WOORARRA': '3962',
+    'WOORARRA EAST': '3962',
+    'PORT FRANKLIN': '3964',
+    'PORT WELSHPOOL': '3965',
+    'BINGINWARRI': '3966',
+    'HAZEL PARK': '3966',
+    'WELSHPOOL': '3966',
+    'HEDLEY': '3967',
+    'ALBERTON': '3971',
+    'ALBERTON WEST': '3971',
+    'BALOOK': '3971',
+    'CALROSSIE': '3971',
+    'DEVON NORTH': '3971',
+    'GELLIONDALE': '3971',
+    'HIAWATHA': '3971',
+    'HUNTERSTON': '3971',
+    'JACK RIVER': '3971',
+    'LANGSBOROUGH': '3971',
+    'MACKS CREEK': '3971',
+    'MADALYA': '3971',
+    'MANNS BEACH': '3971',
+    'PORT ALBERT': '3971',
+    'ROBERTSONS BEACH': '3971',
+    'SNAKE ISLAND': '3971',
+    'STACEYS BRIDGE': '3971',
+    'TARRA VALLEY': '3971',
+    'TARRAVILLE': '3971',
+    'WON WRON': '3971',
+    'YARRAM': '3971',
+    'LYNBROOK': '3975',
+    'HAMPTON PARK': '3976',
+    'CANNONS CREEK': '3977',
+    'CRANBOURNE': '3977',
+    'CRANBOURNE EAST': '3977',
+    'CRANBOURNE NORTH': '3977',
+    'CRANBOURNE SOUTH': '3977',
+    'CRANBOURNE WEST': '3977',
+    'DEVON MEADOWS': '3977',
+    'JUNCTION VILLAGE': '3977',
+    'SANDHURST': '3977',
+    'SKYE': '3977',
+    'CARDINIA': '3978',
+    'CLYDE NORTH': '3978',
+    'ALMURTA': '3979',
+    'GLEN ALVIE': '3979',
+    'KERNOT': '3979',
+    'BLIND BIGHT': '3980',
+    'TOORADIN': '3980',
+    'WARNEET': '3980',
+    'BAYLES': '3981',
+    'CATANI': '3981',
+    'DALMORE': '3981',
+    'HEATH HILL': '3981',
+    'KOO WEE RUP': '3981',
+    'KOO WEE RUP NORTH': '3981',
+    'YANNATHAN': '3981',
+    'ADAMS ESTATE': '3984',
+    'CALDERMEADE': '3984',
+    'CORONET BAY': '3984',
+    'GRANTVILLE': '3984',
+    'JAM JERRUP': '3984',
+    'LANG LANG': '3984',
+    'LANG LANG EAST': '3984',
+    'MONOMEITH': '3984',
+    'PIONEER BAY': '3984',
+    'QUEENSFERRY': '3984',
+    'TENBY POINT': '3984',
+    'THE GURDIES': '3984',
+    'POOWONG': '3988',
+    'POOWONG EAST': '3988',
+    'POOWONG NORTH': '3988',
+    'GLEN FORBES': '3990',
+    'BASS': '3991',
+    'BLACKWOOD FOREST': '3992',
+    'DALYSTON': '3992',
+    'RYANSTON': '3992',
+    'WEST CREEK': '3992',
+    'ANDERSON': '3995',
+    'ARCHIES CREEK': '3995',
+    'CAPE PATERSON': '3995',
+    'HARMERS HAVEN': '3995',
+    'HICKSBOROUGH': '3995',
+    'KILCUNDA': '3995',
+    'LANCE CREEK': '3995',
+    'NORTH WONTHAGGI': '3995',
+    'POWLETT RIVER': '3995',
+    'SOUTH DUDLEY': '3995',
+    'WATTLE BANK': '3995',
+    'WONTHAGGI': '3995',
+    'WOOLAMAI': '3995',
+    'INVERLOCH': '3996',
+    'POUND CREEK': '3996',
+    'BRISBANE': '4000',
+    'BRISBANE ADELAIDE STREET': '4000',
+    'BRISBANE GPO': '4000',
+    'NEW FARM': '4005',
+    'BOWEN HILLS': '4006',
+    'FORTITUDE VALLEY': '4006',
+    'FORTITUDE VALLEY BC': '4006',
+    'HERSTON': '4006',
+    'HAMILTON CENTRAL': '4007',
+    'PINKENBA': '4008',
+    'EAGLE FARM': '4009',
+    'EAGLE FARM BC': '4009',
+    'ALBION BC': '4010',
+    'ALBION DC': '4010',
+    'CLAYFIELD': '4011',
+    'HENDRA': '4011',
+    'NUNDAH': '4012',
+    'TOOMBUL': '4012',
+    'WAVELL HEIGHTS': '4012',
+    'WAVELL HEIGHTS NORTH': '4012',
+    'NORTHGATE': '4013',
+    'BANYO': '4014',
+    'NUDGEE': '4014',
+    'NUDGEE BEACH': '4014',
+    'VIRGINIA BC': '4014',
+    'VIRGINIA DC': '4014',
+    'BRACKEN RIDGE': '4017',
+    'BRIGHTON EVENTIDE': '4017',
+    'BRIGHTON NATHAN STREET': '4017',
+    'DEAGON': '4017',
+    'SANDGATE DC': '4017',
+    'SHORNCLIFFE': '4017',
+    'FITZGIBBON': '4018',
+    'TAIGUM': '4018',
+    'CLONTARF BEACH': '4019',
+    'CLONTARF DC': '4019',
+    'MARGATE': '4019',
+    'MARGATE BEACH': '4019',
+    'WOODY POINT': '4019',
+    'REDCLIFFE': '4020',
+    'REDCLIFFE NORTH': '4020',
+    'KIPPARING': '4021',
+    'ROTHWELL': '4022',
+    'BULWER': '4025',
+    'CAPE MORETON': '4025',
+    'COWAN COWAN': '4025',
+    'TANGALOOMA': '4025',
+    'LUTWYCHE': '4030',
+    'WOOLOOWIN': '4030',
+    'GORDON PARK': '4031',
+    'KEDRON': '4031',
+    'CHERMSIDE': '4032',
+    'CHERMSIDE BC': '4032',
+    'CHERMSIDE CENTRE': '4032',
+    'CHERMSIDE SOUTH': '4032',
+    'CHERMSIDE WEST': '4032',
+    'ASPLEY': '4034',
+    'BOONDALL': '4034',
+    'CARSELDINE': '4034',
+    'GEEBUNG': '4034',
+    'ZILLMERE': '4034',
+    'ALBANY CREEK': '4035',
+    'BRIDGEMAN DOWNS': '4035',
+    'EATONS HILL': '4037',
+    'ALDERLEY': '4051',
+    'ENOGGERA': '4051',
+    'GAYTHORNE': '4051',
+    'GRANGE': '4051',
+    'NEWMARKET': '4051',
+    'WILSTON': '4051',
+    'BROOKSIDE CENTRE': '4053',
+    'EVERTON HILLS': '4053',
+    'EVERTON PARK': '4053',
+    'MCDOWALL': '4053',
+    'MITCHELTON': '4053',
+    'STAFFORD': '4053',
+    'STAFFORD BC': '4053',
+    'STAFFORD DC': '4053',
+    'STAFFORD HEIGHTS': '4053',
+    'ARANA HILLS': '4054',
+    'KEPERRA': '4054',
+    'BUNYA': '4055',
+    'FERNY GROVE': '4055',
+    'FERNY HILLS': '4055',
+    'FERNY HILLS DC': '4055',
+    'UPPER KEDRON': '4055',
+    'KELVIN GROVE': '4059',
+    'KELVIN GROVE BC': '4059',
+    'KELVIN GROVE DC': '4059',
+    'ASHGROVE': '4060',
+    'ASHGROVE WEST': '4060',
+    'MILTON BC': '4064',
+    'BARDON': '4065',
+    'AUCHENFLOWER': '4066',
+    'MOUNT COOTTHA': '4066',
+    'TOOWONG': '4066',
+    'TOOWONG BC': '4066',
+    'TOOWONG DC': '4066',
+    'ST LUCIA': '4067',
+    'ST LUCIA SOUTH': '4067',
+    'CHELMER': '4068',
+    'INDOOROOPILLY': '4068',
+    'INDOOROOPILLY CENTRE': '4068',
+    'TARINGA': '4068',
+    'CHAPEL HILL': '4069',
+    'FIG TREE POCKET': '4069',
+    'KENMORE': '4069',
+    'KENMORE DC': '4069',
+    'KENMORE EAST': '4069',
+    'KENMORE HILLS': '4069',
+    'PINJARRA HILLS': '4069',
+    'PULLENVALE': '4069',
+    'UPPER BROOKFIELD': '4069',
+    'ANSTEAD': '4070',
+    'BELLBOWRIE': '4070',
+    'MOGGILL': '4070',
+    'SEVENTEEN MILE ROCKS': '4073',
+    'SINNAMON PARK': '4073',
+    'JAMBOREE HEIGHTS': '4074',
+    'JINDALEE': '4074',
+    'MOUNT OMMANEY': '4074',
+    'RIVERHILLS': '4074',
+    'SUMNER': '4074',
+    'SUMNER PARK BC': '4074',
+    'WESTLAKE': '4074',
+    'CORINDA': '4075',
+    'GRACEVILLE': '4075',
+    'GRACEVILLE EAST': '4075',
+    'DARRA': '4076',
+    'WACOL': '4076',
+    'DOOLANDELLA': '4077',
+    'INALA': '4077',
+    'INALA EAST': '4077',
+    'INALA HEIGHTS': '4077',
+    'RICHLANDS DC': '4077',
+    'ELLEN GROVE': '4078',
+    'FOREST LAKE': '4078',
+    'HIGHGATE HILL': '4101',
+    'SOUTH BRISBANE': '4101',
+    'SOUTH BRISBANE BC': '4101',
+    'WEST END': '4101',
+    'BURANDA': '4102',
+    'DUTTON PARK': '4102',
+    'WOOLLOONGABBA': '4102',
+    'ANNERLEY': '4103',
+    'ANNERLEY DC': '4103',
+    'FAIRFIELD GARDENS': '4103',
+    'YERONGA': '4104',
+    'MOOROOKA': '4105',
+    'YEERONGPILLY': '4105',
+    'BRISBANE MARKET': '4106',
+    'ROCKLEA': '4106',
+    'ROCKLEA DC': '4106',
+    'SALISBURY EAST': '4107',
+    'ARCHERFIELD': '4108',
+    'ARCHERFIELD BC': '4108',
+    'COOPERS PLAINS': '4108',
+    'SUNNYBANK': '4109',
+    'SUNNYBANK HILLS': '4109',
+    'SUNNYBANK SOUTH': '4109',
+    'ACACIA RIDGE': '4110',
+    'ACACIA RIDGE BC': '4110',
+    'ACACIA RIDGE DC': '4110',
+    'HEATHWOOD': '4110',
+    'PALLARA': '4110',
+    'WILLAWONG': '4110',
+    'NATHAN': '4111',
+    'KURABY': '4112',
+    'EIGHT MILE PLAINS': '4113',
+    'RUNCORN': '4113',
+    'LOGAN CENTRAL': '4114',
+    'LOGAN CITY BC': '4114',
+    'LOGAN CITY DC': '4114',
+    'WOODRIDGE': '4114',
+    'ALGESTER': '4115',
+    'PARKINSON': '4115',
+    'CALAMVALE': '4116',
+    'DREWVALE': '4116',
+    'STRETTON': '4116',
+    'BERRINBA': '4117',
+    'KARAWATHA': '4117',
+    'BROWNS PLAINS BC': '4118',
+    'FORESTDALE': '4118',
+    'HERITAGE PARK': '4118',
+    'UNDERWOOD': '4119',
+    'GREENSLOPES': '4120',
+    'STONES CORNER': '4120',
+    'HOLLAND PARK': '4121',
+    'HOLLAND PARK EAST': '4121',
+    'HOLLAND PARK WEST': '4121',
+    'TARRAGINDI': '4121',
+    'WELLERS HILL': '4121',
+    'MANSFIELD BC': '4122',
+    'MANSFIELD DC': '4122',
+    'MOUNT GRAVATT': '4122',
+    'MOUNT GRAVATT EAST': '4122',
+    'UPPER MOUNT GRAVATT': '4122',
+    'UPPER MOUNT GRAVATT BC': '4122',
+    'ROCHEDALE': '4123',
+    'ROCHEDALE SOUTH': '4123',
+    'BORONIA HEIGHTS': '4124',
+    'GREENBANK': '4124',
+    'NEW BEITH': '4124',
+    'MUNRUBEN': '4125',
+    'PARK RIDGE': '4125',
+    'PARK RIDGE SOUTH': '4125',
+    'PRIESTDALE': '4127',
+    'SLACKS CREEK': '4127',
+    'SHAILER PARK': '4128',
+    'TANAH MERAH': '4128',
+    'LOGANHOLME': '4129',
+    'LOGANHOLME BC': '4129',
+    'LOGANHOLME DC': '4129',
+    'CARBROOK': '4130',
+    'CORNUBIA': '4130',
+    'LOGANLEA': '4131',
+    'MEADOWBROOK': '4131',
+    'CRESTMEAD': '4132',
+    'CRESTMEAD DC': '4132',
+    'MARSDEN': '4132',
+    'CHAMBERS FLAT': '4133',
+    'LOGAN RESERVE': '4133',
+    'WATERFORD WEST': '4133',
+    'COORPAROO': '4151',
+    'COORPAROO BC': '4151',
+    'COORPAROO DC': '4151',
+    'CAMP HILL': '4152',
+    'CARINA HEIGHTS': '4152',
+    'CARINDALE': '4152',
+    'GUMDALE': '4154',
+    'RANSOME': '4154',
+    'WAKERLEY': '4154',
+    'CHANDLER': '4155',
+    'BURBANK': '4156',
+    'MACKENZIE': '4156',
+    'CAPALABA': '4157',
+    'CAPALABA BC': '4157',
+    'CAPALABA DC': '4157',
+    'CAPALABA WEST': '4157',
+    'SHELDON': '4157',
+    'THORNESIDE': '4158',
+    'BIRKDALE': '4159',
+    'ORMISTON': '4160',
+    'WELLINGTON POINT': '4160',
+    'ALEXANDRA HILLS': '4161',
+    'CLEVELAND DC': '4163',
+    'THORNLANDS': '4164',
+    'MOUNT COTTON': '4165',
+    'REDLAND BAY': '4165',
+    'VICTORIA POINT WEST': '4165',
+    'EAST BRISBANE': '4169',
+    'CANNON HILL': '4170',
+    'MORNINGSIDE': '4170',
+    'NORMAN PARK': '4170',
+    'BULIMBA': '4171',
+    'HAWTHORNE': '4171',
+    'MURARRIE': '4172',
+    'TINGALPA': '4173',
+    'TINGALPA BC': '4173',
+    'TINGALPA DC': '4173',
+    'HEMMANT': '4174',
+    'LYTTON': '4178',
+    'PORT OF BRISBANE': '4178',
+    'WYNNUM': '4178',
+    'WYNNUM NORTH': '4178',
+    'WYNNUM PLAZA': '4178',
+    'WYNNUM WEST': '4178',
+    'LOTA': '4179',
+    'MANLY WEST': '4179',
+    'AMITY': '4183',
+    'AMITY POINT': '4183',
+    'DUNWICH': '4183',
+    'NORTH STRADBROKE ISLAND': '4183',
+    'POINT LOOKOUT': '4183',
+    'COOCHIEMUDLO ISLAND': '4184',
+    'KARRAGARRA ISLAND': '4184',
+    'LAMB ISLAND': '4184',
+    'MACLEAY ISLAND': '4184',
+    'PEEL ISLAND': '4184',
+    'PERULPA ISLAND': '4184',
+    'RUSSELL ISLAND': '4184',
+    'BETHANIA': '4205',
+    'BAHRS SCRUB': '4207',
+    'BEENLEIGH': '4207',
+    'BELIVAH': '4207',
+    'BUCCAN': '4207',
+    'EAGLEBY': '4207',
+    'EDENS LANDING': '4207',
+    'HOLMVIEW': '4207',
+    'LOGAN VILLAGE': '4207',
+    'LUSCOMBE': '4207',
+    'MOUNT WARREN PARK': '4207',
+    'STAPYLTON': '4207',
+    'WINDAROO': '4207',
+    'WOLFFDENE': '4207',
+    'WOONGOOLBA': '4207',
+    'YARRABILBA': '4207',
+    'YATALA': '4207',
+    'YATALA DC': '4207',
+    'JACOBS WELL': '4208',
+    'KINGSHOLME': '4208',
+    'NORWELL': '4208',
+    'ORMEAU': '4208',
+    'ORMEAU HILLS': '4208',
+    'COOMERA': '4209',
+    'PIMPAMA': '4209',
+    'UPPER COOMERA': '4209',
+    'GUANABA': '4210',
+    'MAUDSLAND': '4210',
+    'OXENFORD': '4210',
+    'STUDIO VILLAGE': '4210',
+    'WONGAWALLAN': '4210',
+    'ADVANCETOWN': '4211',
+    'BEECHMONT': '4211',
+    'CARRARA': '4211',
+    'CLAGIRABA': '4211',
+    'GAVEN': '4211',
+    'GILSTON': '4211',
+    'HIGHLAND PARK': '4211',
+    'LOWER BEECHMONT': '4211',
+    'MOUNT NATHAN': '4211',
+    'NATURAL BRIDGE': '4211',
+    'NERANG': '4211',
+    'NERANG BC': '4211',
+    'NERANG DC': '4211',
+    'NUMINBAH VALLEY': '4211',
+    "O'REILLY": '4211',
+    'PACIFIC PINES': '4211',
+    'SOUTHERN LAMINGTON': '4211',
+    'HELENSVALE': '4212',
+    'HELENSVALE TOWN CENTRE': '4212',
+    'HOPE ISLAND': '4212',
+    'SANCTUARY COVE': '4212',
+    'AUSTINVILLE': '4213',
+    'BONOGIN': '4213',
+    'MUDGEERABA': '4213',
+    'SPRINGBROOK': '4213',
+    'TALLAI': '4213',
+    'WORONGARY': '4213',
+    'ARUNDEL': '4214',
+    'ARUNDEL BC': '4214',
+    'ARUNDEL DC': '4214',
+    'ASHMORE': '4214',
+    'ASHMORE CITY': '4214',
+    'MOLENDINAR': '4214',
+    'PARKWOOD': '4214',
+    'AUSTRALIA FAIR': '4215',
+    'CHIRN PARK': '4215',
+    'LABRADOR': '4215',
+    'SOUTHPORT': '4215',
+    'SOUTHPORT BC': '4215',
+    'SOUTHPORT PARK': '4215',
+    'BIGGERA WATERS': '4216',
+    'COOMBABAH': '4216',
+    'HOLLYWELL': '4216',
+    'PARADISE POINT': '4216',
+    'RUNAWAY BAY': '4216',
+    'SOUTH STRADBROKE': '4216',
+    'BENOWA': '4217',
+    'BUNDALL': '4217',
+    'BUNDALL BC': '4217',
+    'BUNDALL DC': '4217',
+    'CHEVRON ISLAND': '4217',
+    'GOLD COAST MC': '4217',
+    'ISLE OF CAPRI': '4217',
+    'MAIN BEACH': '4217',
+    'SURFERS PARADISE': '4217',
+    'BROADBEACH': '4218',
+    'BROADBEACH WATERS': '4218',
+    'MERMAID BEACH': '4218',
+    'MERMAID WATERS': '4218',
+    'NOBBY BEACH': '4218',
+    'PACIFIC FAIR': '4218',
+    'Q SUPERCENTRE': '4218',
+    'WEST BURLEIGH': '4219',
+    'BURLEIGH BC': '4220',
+    'BURLEIGH DC': '4220',
+    'BURLEIGH HEADS': '4220',
+    'BURLEIGH TOWN': '4220',
+    'BURLEIGH WATERS': '4220',
+    'MIAMI': '4220',
+    'ELANORA': '4221',
+    'CURRUMBIN': '4223',
+    'CURRUMBIN DC': '4223',
+    'CURRUMBIN VALLEY': '4223',
+    'CURRUMBIN WATERS': '4223',
+    'TUGUN': '4224',
+    'TUGUN HEIGHTS': '4224',
+    'BILINGA': '4225',
+    'CLEAR ISLAND WATERS': '4226',
+    'MERRIMAC': '4226',
+    'ROBINA': '4226',
+    'ROBINA DC': '4226',
+    'VARSITY LAKES': '4227',
+    'TALLEBUDGERA': '4228',
+    'TALLEBUDGERA VALLEY': '4228',
+    'ROBINA TOWN CENTRE': '4230',
+    'TAMBORINE': '4270',
+    'EAGLE HEIGHTS': '4271',
+    'MOUNT TAMBORINE': '4272',
+    'NORTH TAMBORINE': '4272',
+    'TAMBORINE MOUNTAIN': '4272',
+    'BENOBBLE': '4275',
+    'BIDDADDABA': '4275',
+    'BOYLAND': '4275',
+    'CANUNGRA': '4275',
+    'FERNY GLEN': '4275',
+    'ILLINBAH': '4275',
+    'LAMINGTON NATIONAL PARK': '4275',
+    'SARABAH': '4275',
+    'WITHEREN': '4275',
+    'WONGLEPONG': '4275',
+    'JIMBOOMBA': '4280',
+    'NORTH MACLEAN': '4280',
+    'SOUTH MACLEAN': '4280',
+    'STOCKLEIGH': '4280',
+    'ALLENVIEW': '4285',
+    'BEAUDESERT': '4285',
+    'BIRNAM': '4285',
+    'BROMELTON': '4285',
+    'CAINBABLE': '4285',
+    'CEDAR GROVE': '4285',
+    'CEDAR VALE': '4285',
+    'CHINGHEE CREEK': '4285',
+    'CHRISTMAS CREEK': '4285',
+    'CRYNA': '4285',
+    'GLENEAGLE': '4285',
+    'HILLVIEW': '4285',
+    'INNISPLAIN': '4285',
+    'JOSEPHVILLE': '4285',
+    'KAGARU': '4285',
+    'KERRY': '4285',
+    'KNAPP CREEK': '4285',
+    'KOORALBYN': '4285',
+    'LAMINGTON': '4285',
+    'LARAVALE': '4285',
+    'MUNDOOLUN': '4285',
+    'NINDOOINBAH': '4285',
+    'OAKY CREEK': '4285',
+    'TABOOBA': '4285',
+    'TABRAGALBA': '4285',
+    'TAMROOKUM': '4285',
+    'TAMROOKUM CREEK': '4285',
+    'UNDULLAH': '4285',
+    'VERESDALE': '4285',
+    'VERESDALE SCRUB': '4285',
+    'BARNEY VIEW': '4287',
+    'MOUNT BARNEY': '4287',
+    'MOUNT LINDESAY': '4287',
+    'PALEN CREEK': '4287',
+    'RATHDOWNEY': '4287',
+    'AUGUSTINE HEIGHTS': '4300',
+    'BELLBIRD PARK': '4300',
+    'BROOKWATER': '4300',
+    'CAROLE PARK': '4300',
+    'GAILES': '4300',
+    'GOODNA': '4300',
+    'GOODNA DC': '4300',
+    'SPRINGFIELD LAKES': '4300',
+    'COLLINGWOOD PARK': '4301',
+    'REDBANK PLAINS': '4301',
+    'DINMORE': '4303',
+    'NEW CHUM': '4303',
+    'BLACKSTONE': '4304',
+    'BOOVAL': '4304',
+    'BOOVAL BC': '4304',
+    'BOOVAL DC': '4304',
+    'BOOVAL FAIR': '4304',
+    'BUNDAMBA': '4304',
+    'EBBW VALE': '4304',
+    'NORTH BOOVAL': '4304',
+    'SILKSTONE': '4304',
+    'BASIN POCKET': '4305',
+    'BRASSALL': '4305',
+    'BREMER': '4305',
+    'COALFALLS': '4305',
+    'EAST IPSWICH': '4305',
+    'EASTERN HEIGHTS': '4305',
+    'FLINDERS VIEW': '4305',
+    'IPSWICH': '4305',
+    'LIMESTONE RIDGES': '4305',
+    'MOORES POCKET': '4305',
+    'NORTH IPSWICH': '4305',
+    'NORTH TIVOLI': '4305',
+    'RACEVIEW': '4305',
+    'SADLIERS CROSSING': '4305',
+    'TIVOLI': '4305',
+    'WEST IPSWICH': '4305',
+    'WULKURAKA': '4305',
+    'YAMANTO': '4305',
+    'AMBERLEY': '4306',
+    'AVOCA VALE': '4306',
+    'BANKS CREEK': '4306',
+    'BARELLAN POINT': '4306',
+    'BENARKIN': '4306',
+    'BENARKIN NORTH': '4306',
+    'BLACKBUTT NORTH': '4306',
+    'BLACKBUTT SOUTH': '4306',
+    'BLACKSOIL': '4306',
+    'BORALLON': '4306',
+    'CHERRY CREEK': '4306',
+    'CHUWAR': '4306',
+    'DEEBING HEIGHTS': '4306',
+    'ENGLAND CREEK': '4306',
+    'FAIRNEY VIEW': '4306',
+    'GLAMORGAN VALE': '4306',
+    'GOOGA CREEK': '4306',
+    'GOOLMAN': '4306',
+    'HAIGSLEA': '4306',
+    'HARLIN': '4306',
+    'KARALEE': '4306',
+    'KARANA DOWNS': '4306',
+    'KARRABIN': '4306',
+    'KHOLO': '4306',
+    'LAKE MANCHESTER': '4306',
+    'LARK HILL': '4306',
+    'LINVILLE': '4306',
+    'MOORE': '4306',
+    'MOUNT BINGA': '4306',
+    'MOUNT CROSBY': '4306',
+    'MOUNT MARROW': '4306',
+    'MOUNT STANLEY': '4306',
+    'MUIRLEA': '4306',
+    'NUKKU': '4306',
+    'PEAK CROSSING': '4306',
+    'PURGA': '4306',
+    'RIPLEY': '4306',
+    'SOUTH RIPLEY': '4306',
+    'SPLIT YARD CREEK': '4306',
+    'SWANBANK': '4306',
+    'TAROMEO': '4306',
+    'TEELAH': '4306',
+    'THAGOONA': '4306',
+    'VERNOR': '4306',
+    'WALLOON': '4306',
+    'WANORA': '4306',
+    'WEST AMBERLEY': '4306',
+    'WILLOWBANK': '4306',
+    'WIVENHOE POCKET': '4306',
+    'COLEYVILLE': '4307',
+    'HARRISVILLE': '4307',
+    'MUTDAPILLY': '4307',
+    'RADFORD': '4307',
+    'WARRILL VIEW': '4307',
+    'WILSONS PLAINS': '4307',
+    'CHARLWOOD': '4309',
+    'CLUMBER': '4309',
+    'FASSIFERN VALLEY': '4309',
+    'FRAZERVIEW': '4309',
+    'KALBAR': '4309',
+    'KENTS LAGOON': '4309',
+    'KULGUN': '4309',
+    'MILORA': '4309',
+    'MOOGERAH': '4309',
+    'MORWINCHA': '4309',
+    'MOUNT EDWARDS': '4309',
+    'MUNBILLA': '4309',
+    'OBUM OBUM': '4309',
+    'TAROME': '4309',
+    'TEVIOTVILLE': '4309',
+    'ANTHONY': '4310',
+    'BLANTYRE': '4310',
+    'BUNBURRA': '4310',
+    'BUNJURGEN': '4310',
+    'BURNETT CREEK': '4310',
+    'CANNON CREEK': '4310',
+    'CARNEYS CREEK': '4310',
+    'COOCHIN': '4310',
+    'COULSON': '4310',
+    'CROFTBY': '4310',
+    'DUGANDAN': '4310',
+    'FRENCHES CREEK': '4310',
+    'HOYA': '4310',
+    'KENTS POCKET': '4310',
+    'MAROON': '4310',
+    'MILBONG': '4310',
+    'MILFORD': '4310',
+    'MOUNT ALFORD': '4310',
+    'MOUNT FRENCH': '4310',
+    'ROADVALE': '4310',
+    'TEMPLIN': '4310',
+    'WALLACES CREEK': '4310',
+    'WOOLOOMAN': '4310',
+    'WYARALONG': '4310',
+    'ATKINSONS DAM': '4311',
+    'BRIGHTVIEW': '4311',
+    'BUARABA': '4311',
+    'BUARABA SOUTH': '4311',
+    'CHURCHABLE': '4311',
+    'COOLANA': '4311',
+    'COOMINYA': '4311',
+    'LOCKYER WATERS': '4311',
+    'LOWOOD': '4311',
+    'MINDEN': '4311',
+    'MOUNT TARAMPA': '4311',
+    'PATRICK ESTATE': '4311',
+    'PRENZLAU': '4311',
+    'RIFLE RANGE': '4311',
+    'TARAMPA': '4311',
+    'WIVENHOE HILL': '4311',
+    'BRYDEN': '4312',
+    'CABOONBAH': '4312',
+    'COAL CREEK': '4312',
+    'CROSSDALE': '4312',
+    'GLEN ESK': '4312',
+    'MOOMBRIA': '4312',
+    'MOUNT BYRON': '4312',
+    'MOUNT HALLEN': '4312',
+    'MURRUMBA': '4312',
+    'REDBANK CREEK': '4312',
+    'SOMERSET DAM': '4312',
+    'BIARRA': '4313',
+    'BRAEMORE': '4313',
+    'COOEEIMBARDI': '4313',
+    'CRESSBROOK': '4313',
+    'GREGORS CREEK': '4313',
+    'IVORY CREEK': '4313',
+    'LOWER CRESSBROOK': '4313',
+    'MOUNT BEPPO': '4313',
+    'OTTABA': '4313',
+    'SCRUB CREEK': '4313',
+    'TOOGOOLAWAH': '4313',
+    'YIMBUN': '4313',
+    'ASHWELL': '4340',
+    'GRANDCHESTER': '4340',
+    'JEEBROPILLY': '4340',
+    'LANEFIELD': '4340',
+    'LOWER MOUNT WALKER': '4340',
+    'MERRYVALE': '4340',
+    'MOORANG': '4340',
+    'MOUNT FORBES': '4340',
+    'MOUNT MORT': '4340',
+    'MOUNT WALKER': '4340',
+    'MOUNT WALKER WEST': '4340',
+    'ROSEVALE': '4340',
+    'TALLEGALLA': '4340',
+    'THE BLUFF': '4340',
+    'BLENHEIM': '4341',
+    'HATTON VALE': '4341',
+    'KENSINGTON GROVE': '4341',
+    'KENTVILLE': '4341',
+    'LAIDLEY': '4341',
+    'LAIDLEY CREEK WEST': '4341',
+    'LAIDLEY HEIGHTS': '4341',
+    'LAIDLEY NORTH': '4341',
+    'LAIDLEY SOUTH': '4341',
+    'MOUNT BERRYMAN': '4341',
+    'MULGOWIE': '4341',
+    'PLAINLAND': '4341',
+    'REGENCY DOWNS': '4341',
+    'SUMMERHOLM': '4341',
+    'TOWNSON': '4341',
+    'CROWLEY VALE': '4342',
+    'GLEN CAIRN': '4342',
+    'GLENORE GROVE': '4342',
+    'LOCKROSE': '4342',
+    'LYNFORD': '4342',
+    'ADARE': '4343',
+    'BLACK DUCK CREEK': '4343',
+    'CAFFEY': '4343',
+    'COLLEGE VIEW': '4343',
+    'EAST HALDON': '4343',
+    'FORDSDALE': '4343',
+    'GATTON': '4343',
+    'INGOLDSBY': '4343',
+    'JUNCTION VIEW': '4343',
+    'LAKE CLARENDON': '4343',
+    'LAWES': '4343',
+    'LEFTHAND BRANCH': '4343',
+    'LOWER TENTHILL': '4343',
+    'MORTON VALE': '4343',
+    'MOUNT SYLVIA': '4343',
+    'PLACID HILLS': '4343',
+    'ROCKSIDE': '4343',
+    'ROPELEY': '4343',
+    'UPPER TENTHILL': '4343',
+    'VINEGAR HILL': '4343',
+    'CARPENDALE': '4344',
+    'EGYPT': '4344',
+    'FLAGSTONE CREEK': '4344',
+    'HELIDON': '4344',
+    'HELIDON SPA': '4344',
+    'IREDALE': '4344',
+    'LOCKYER': '4344',
+    'ROCKMOUNT': '4344',
+    'SEVENTEEN MILE': '4344',
+    'STOCKYARD': '4344',
+    'UPPER FLAGSTONE': '4344',
+    'GATTON COLLEGE': '4345',
+    'MARBURG': '4346',
+    'GRANTHAM': '4347',
+    'MA MA CREEK': '4347',
+    'MOUNT WHITESTONE': '4347',
+    'VERADILLA': '4347',
+    'WINWILL': '4347',
+    'ATHOL': '4350',
+    'BLUE MOUNTAIN HEIGHTS': '4350',
+    'CENTENARY HEIGHTS': '4350',
+    'CLIFFORD GARDENS': '4350',
+    'COTSWOLD HILLS': '4350',
+    'CRANLEY': '4350',
+    'DARLING HEIGHTS': '4350',
+    'DRAYTON': '4350',
+    'DRAYTON NORTH': '4350',
+    'EAST TOOWOOMBA': '4350',
+    'FINNIE': '4350',
+    'GLENVALE': '4350',
+    'GOWRIE MOUNTAIN': '4350',
+    'HARLAXTON': '4350',
+    'HARRISTOWN': '4350',
+    'KEARNEYS SPRING': '4350',
+    'MIDDLE RIDGE': '4350',
+    'MOUNT KYNOCH': '4350',
+    'MOUNT LOFTY': '4350',
+    'MOUNT RASCAL': '4350',
+    'NORTH TOOWOOMBA': '4350',
+    'NORTHLANDS': '4350',
+    'PRINCE HENRY HEIGHTS': '4350',
+    'RANGEVILLE': '4350',
+    'REDWOOD': '4350',
+    'ROCKVILLE': '4350',
+    'SOUTH TOOWOOMBA': '4350',
+    'SOUTHTOWN': '4350',
+    'TOOWOOMBA': '4350',
+    'TOOWOOMBA BC': '4350',
+    'TOOWOOMBA CITY': '4350',
+    'TOOWOOMBA DC': '4350',
+    'TOOWOOMBA EAST': '4350',
+    'TOOWOOMBA SOUTH': '4350',
+    'TOOWOOMBA VILLAGE FAIR': '4350',
+    'TOOWOOMBA WEST': '4350',
+    'TOP CAMP': '4350',
+    'WELLCAMP': '4350',
+    'WILSONTON': '4350',
+    'WILSONTON HEIGHTS': '4350',
+    'WYALLA PLAZA': '4350',
+    'BALLARD': '4352',
+    'BAPAUME': '4352',
+    'BLANCHVIEW': '4352',
+    'BRANCHVIEW': '4352',
+    'CABARLAH': '4352',
+    'CEMENT MILLS': '4352',
+    'COALBANK': '4352',
+    'CONDAMINE PLAINS': '4352',
+    'CUTELLA': '4352',
+    'DERRYMORE': '4352',
+    'DJUAN': '4352',
+    'DOCTOR CREEK': '4352',
+    'EVERGREEN': '4352',
+    'FIFTEEN MILE': '4352',
+    'GEHAM': '4352',
+    'GORE': '4352',
+    'GOWRIE JUNCTION': '4352',
+    'GOWRIE LITTLE PLAIN': '4352',
+    'GRAPETREE': '4352',
+    'GROOMSVILLE': '4352',
+    'HIGHGROVE': '4352',
+    'HODGSON VALE': '4352',
+    'KARARA': '4352',
+    'KLEINTON': '4352',
+    'KULPI': '4352',
+    'KURROWAH': '4352',
+    'LYRA': '4352',
+    'MACLAGAN': '4352',
+    'MALLING': '4352',
+    'MERINGANDAN': '4352',
+    'MERINGANDAN WEST': '4352',
+    'MERRITTS CREEK': '4352',
+    'MOUNT LUKE': '4352',
+    'MUNIGANEEN': '4352',
+    'NARKO': '4352',
+    'NORTH MACLAGAN': '4352',
+    'NUTGROVE': '4352',
+    'OMAN AMA': '4352',
+    'PALMTREE': '4352',
+    'PAMPAS': '4352',
+    'PECHEY': '4352',
+    'PERANGA': '4352',
+    'PERSEVERANCE': '4352',
+    'POSTMANS RIDGE': '4352',
+    'POZIERES': '4352',
+    'RANGEMORE': '4352',
+    'RAVENSBOURNE': '4352',
+    'SILVER RIDGE': '4352',
+    'SPRING BLUFF': '4352',
+    'ST AUBYN': '4352',
+    'THORNVILLE': '4352',
+    'TOOWOOMBA MC': '4352',
+    'TUMMAVILLE': '4352',
+    'UMBIRAM': '4352',
+    'UPPER LOCKYER': '4352',
+    'VALE VIEW': '4352',
+    'WHICHELLO': '4352',
+    'WHITE MOUNTAIN': '4352',
+    'WITHCOTT': '4352',
+    'WOOLMER': '4352',
+    'WUTUL': '4352',
+    'WYREEMA': '4352',
+    'YALANGUR': '4352',
+    'YANDILLA': '4352',
+    'BERGEN': '4353',
+    'EAST COOYAR': '4353',
+    'HADEN': '4353',
+    'GOOMBUNGEE': '4354',
+    'KILBIRNIE': '4354',
+    'ANDURAMBA': '4355',
+    'CRESSBROOK CREEK': '4355',
+    'GLENAVEN': '4355',
+    'JONES GULLY': '4355',
+    'MOUNTAIN CAMP': '4355',
+    'PLAINBY': '4355',
+    'UPPER PINELANDS': '4355',
+    'BONGEEN': '4356',
+    'BROXBURN': '4356',
+    'EVANSLEA': '4356',
+    'IRONGATE': '4356',
+    'KINCORA': '4356',
+    'LINTHORPE': '4356',
+    'MOTLEY': '4356',
+    'MOUNT TYSON': '4356',
+    'NORTH BRANCH': '4356',
+    'NORWIN': '4356',
+    'PITTSWORTH': '4356',
+    'PURRAWUNDA': '4356',
+    'ROSSVALE': '4356',
+    'SCRUBBY MOUNTAIN': '4356',
+    'YARRANLEA': '4356',
+    'BRINGALILY': '4357',
+    'BULLI CREEK': '4357',
+    'CANNING CREEK': '4357',
+    'CAPTAINS MOUNTAIN': '4357',
+    'CONDAMINE FARMS': '4357',
+    'CYPRESS GARDENS': '4357',
+    'DOMVILLE': '4357',
+    'FOREST RIDGE': '4357',
+    'GRAYS GATE': '4357',
+    'KOOROONGARRA': '4357',
+    'LAVELLE': '4357',
+    'LEMONTREE': '4357',
+    'MILLMERRAN': '4357',
+    'MILLMERRAN DOWNS': '4357',
+    'MILLMERRAN WOODS': '4357',
+    'MILLWOOD': '4357',
+    'MOUNT EMLYN': '4357',
+    'PUNCHS CREEK': '4357',
+    'TURALLIN': '4357',
+    'WESTERN CREEK': '4357',
+    'WOONDUL': '4357',
+    'CAMBOOYA': '4358',
+    'FELTON': '4358',
+    'FELTON SOUTH': '4358',
+    'RAMSAY': '4358',
+    'BUDGEE': '4359',
+    'EAST GREENMOUNT': '4359',
+    'GREENMOUNT': '4359',
+    'HIRSTGLEN': '4359',
+    'WEST HALDON': '4359',
+    'NOBBY': '4360',
+    'BACK PLAINS': '4361',
+    'HEADINGTON HILL': '4361',
+    'KINGS CREEK': '4361',
+    'MANAPOURI': '4361',
+    'MISSEN FLAT': '4361',
+    'MOUNT MOLAR': '4361',
+    'NEVILTON': '4361',
+    'PILTON': '4361',
+    'RYEFORD': '4361',
+    'SANDY CAMP': '4361',
+    'UPPER PILTON': '4361',
+    'VICTORIA HILL': '4361',
+    'ALLORA': '4362',
+    'BERAT': '4362',
+    'DEUCHAR': '4362',
+    'ELLINTHORP': '4362',
+    'GOOMBURRA': '4362',
+    'HENDON': '4362',
+    'MOUNT MARSHALL': '4362',
+    'TALGAI': '4362',
+    'SOUTHBROOK': '4363',
+    'BROOKSTEAD': '4364',
+    'LEYBURN': '4365',
+    'ALLAN': '4370',
+    'BONY MOUNTAIN': '4370',
+    'CANNINGVALE': '4370',
+    'CHERRY GULLY': '4370',
+    'CLINTONVALE': '4370',
+    'CUNNINGHAM': '4370',
+    'DANDEROO': '4370',
+    'ELBOW VALLEY': '4370',
+    'FREESTONE': '4370',
+    'GLENGALLAN': '4370',
+    'GREYMARE': '4370',
+    'JUNABEE': '4370',
+    'LESLIE': '4370',
+    'LESLIE DAM': '4370',
+    'LOCH LOMOND': '4370',
+    'MASSIE': '4370',
+    'MORGAN PARK': '4370',
+    'MOUNT COLLIERY': '4370',
+    'MOUNT TABOR': '4370',
+    'MURRAYS BRIDGE': '4370',
+    'PRATTEN': '4370',
+    'RODGERS CREEK': '4370',
+    'ROSENTHAL HEIGHTS': '4370',
+    'SILVERWOOD': '4370',
+    'SLADEVALE': '4370',
+    'THANE': '4370',
+    'THANES CREEK': '4370',
+    'THE HERMITAGE': '4370',
+    'TOOLBURRA': '4370',
+    'TREGONY': '4370',
+    'UPPER FREESTONE': '4370',
+    'UPPER WHEATVALE': '4370',
+    'WARWICK': '4370',
+    'WARWICK DC': '4370',
+    'WHEATVALE': '4370',
+    'WILDASH': '4370',
+    'WIYARRA': '4370',
+    'WOMINA': '4370',
+    'EMU VALE': '4371',
+    'SWANFELS': '4371',
+    'YANGAN': '4371',
+    'TANNYMOREL': '4372',
+    'THE FALLS': '4373',
+    'THE HEAD': '4373',
+    'DALVEEN': '4374',
+    'COTTONVALE': '4375',
+    'FLEURBAIX': '4375',
+    'THULIMBAH': '4376',
+    'GLEN NIVEN': '4377',
+    'THE SUMMIT': '4377',
+    'APPLETHORPE': '4378',
+    'AMIENS': '4380',
+    'DALCOUTH': '4380',
+    'EUKEY': '4380',
+    'KYOOMBA': '4380',
+    'MOUNT TULLY': '4380',
+    'NUNDUBBERMERE': '4380',
+    'PIKEDALE': '4380',
+    'PIKES CREEK': '4380',
+    'SEVERNLEA': '4380',
+    'STANTHORPE': '4380',
+    'STORM KING': '4380',
+    'THORNDALE': '4380',
+    'GLEN APLIN': '4381',
+    'BALLANDEAN': '4382',
+    'SOMME': '4382',
+    'WYBERBA': '4382',
+    'WALLANGARRA': '4383',
+    'LIMEVALE': '4384',
+    'BEEBO': '4385',
+    'GLENARBON': '4385',
+    'MAIDENHEAD': '4385',
+    'RIVERTON': '4385',
+    'SILVER SPUR': '4385',
+    'SMITHLEA': '4385',
+    'TEXAS': '4385',
+    'WATSONS CROSSING': '4385',
+    'BRUSH CREEK': '4387',
+    'BYBERA': '4387',
+    'COOLMUNDA': '4387',
+    'GREENUP': '4387',
+    'MOSQUITO CREEK': '4387',
+    'TERRICA': '4387',
+    'WHETSTONE': '4387',
+    'KURUMBUL': '4388',
+    'YELARBON': '4388',
+    'BILLA BILLA': '4390',
+    'CALINGUNEE': '4390',
+    'CALLANDOON': '4390',
+    'GOODAR': '4390',
+    'GOONDIWINDI': '4390',
+    'KINDON': '4390',
+    'LUNDAVRA': '4390',
+    'WONDALLI': '4390',
+    'WYAGA': '4390',
+    'YAGABURNE': '4390',
+    'KINGSTHORPE': '4400',
+    'ACLAND': '4401',
+    'AUBIGNY': '4401',
+    'BALGOWAN': '4401',
+    'BIDDESTON': '4401',
+    'BOODUA': '4401',
+    'DEVON PARK': '4401',
+    'GREENWOOD': '4401',
+    'HIGHLAND PLAINS': '4401',
+    'KELVINHAUGH': '4401',
+    'MOUNT IRVING': '4401',
+    'MULDU': '4401',
+    'OAKEY': '4401',
+    'ROSALIE PLAINS': '4401',
+    'SABINE': '4401',
+    'SILVERLEIGH': '4401',
+    'YARGULLEN': '4401',
+    'COOYAR': '4402',
+    'KOORALGIN': '4402',
+    'UPPER COOYAR CREEK': '4402',
+    'BRYMAROO': '4403',
+    'JONDARYAN': '4403',
+    'MALU': '4403',
+    'MOUNT MORIAH': '4403',
+    'QUINALOW': '4403',
+    'WEST PRAIRIE': '4403',
+    'BOWENVILLE': '4404',
+    'FORMARTIN': '4404',
+    'IRVINGDALE': '4404',
+    'WAINUI': '4404',
+    'BUNYA MOUNTAINS': '4405',
+    'DALBY': '4405',
+    'DUCKLO': '4405',
+    'MARMADUA': '4405',
+    'PIRRINUAN': '4405',
+    'RANGES BRIDGE': '4405',
+    'ST RUTH': '4405',
+    'TIPTON': '4405',
+    'WERANGA': '4405',
+    'BOONDANDILLA': '4406',
+    'HANNAFORD': '4406',
+    'JIMBOUR': '4406',
+    'KAIMKILLENBUN': '4406',
+    'KOGAN': '4406',
+    'MACALISTER': '4406',
+    'MOONIE': '4406',
+    'SOUTHWOOD': '4406',
+    'THE GUMS': '4406',
+    'WEIR RIVER': '4406',
+    'CECIL PLAINS': '4407',
+    'NANGWEE': '4407',
+    'JANDOWAE': '4410',
+    'WARRA': '4411',
+    'BRIGALOW': '4412',
+    'BAKING BOARD': '4413',
+    'BOONARGA': '4413',
+    'BURNCLUITH': '4413',
+    'CANAGA': '4413',
+    'CHANCES PLAIN': '4413',
+    'CHINCHILLA': '4413',
+    'DURAH': '4413',
+    'HOPELAND': '4413',
+    'WIEAMBILLA': '4413',
+    'COLUMBOOLA': '4415',
+    'DALWOGON': '4415',
+    'GURULMUNDI': '4415',
+    'HOOKSWOOD': '4415',
+    'KOWGURAN': '4415',
+    'MILES': '4415',
+    'PELHAM': '4415',
+    'BARRAMORNIE': '4416',
+    'CONDAMINE': '4416',
+    'MORABY': '4416',
+    'NANGRAM': '4416',
+    'PINE HILLS': '4416',
+    'YULABILLA': '4416',
+    'NOORINDOO': '4417',
+    'OBERINA': '4417',
+    'PARKNOOK': '4417',
+    'SURAT': '4417',
+    'WARKON': '4417',
+    'WELLESLEY': '4417',
+    'WERIBONE': '4417',
+    'GULUGUBA': '4418',
+    'GROSMONT': '4419',
+    'WANDOAN': '4419',
+    'BROADMERE': '4420',
+    'COORADA': '4420',
+    'GHINGHINDA': '4420',
+    'GLENHAUGHTON': '4420',
+    'GWAMBEGWINE': '4420',
+    'HORNET BANK': '4420',
+    'PEEKADOO': '4420',
+    'TAROOM': '4420',
+    'GORANBA': '4421',
+    'COOMRITH': '4422',
+    'FLINTON': '4422',
+    'INGLESTONE': '4422',
+    'MEANDARRA': '4422',
+    'WESTMAR': '4422',
+    'GLENMORGAN': '4423',
+    'TEELBA': '4423',
+    'DRILLHAM': '4424',
+    'DRILLHAM SOUTH': '4424',
+    'GLENAUBYN': '4424',
+    'BOGANDILLA': '4425',
+    'DULACCA': '4425',
+    'JACKSON': '4426',
+    'JACKSON NORTH': '4426',
+    'JACKSON SOUTH': '4426',
+    'CLIFFORD': '4427',
+    'YULEBA': '4427',
+    'YULEBA NORTH': '4427',
+    'YULEBA SOUTH': '4427',
+    'PICKANJINNIE': '4428',
+    'WALLUMBILLA': '4428',
+    'WALLUMBILLA NORTH': '4428',
+    'WALLUMBILLA SOUTH': '4428',
+    'BAFFLE WEST': '4454',
+    'BEILBA': '4454',
+    'DURHAM DOWNS': '4454',
+    'HUTTON CREEK': '4454',
+    'INJUNE': '4454',
+    'PONY HILLS': '4454',
+    'UPPER DAWSON': '4454',
+    'WESTGROVE': '4454',
+    'BALLAROO': '4455',
+    'BLYTHDALE': '4455',
+    'BUNGEWORGORAI': '4455',
+    'BYMOUNT': '4455',
+    'CORNWALL': '4455',
+    'DARGAL ROAD': '4455',
+    'EUMAMURRIN': '4455',
+    'EUTHULLA': '4455',
+    'GUNNEWIN': '4455',
+    'HODGSON': '4455',
+    'MOOGA': '4455',
+    'MOUNT ABUNDANCE': '4455',
+    'MOUNT BINDANGO': '4455',
+    'ORALLO': '4455',
+    'ORANGE HILL': '4455',
+    'ROMA': '4455',
+    'TINGUN': '4455',
+    'WYCOMBE': '4455',
+    'MUCKADILLA': '4461',
+    'AMBY': '4462',
+    'FORESTVALE': '4465',
+    'V GATE': '4465',
+    'WOMALILLA': '4465',
+    'MUNGALLALA': '4467',
+    'REDFORD': '4467',
+    'TYRCONNEL': '4467',
+    'CLARA CREEK': '4468',
+    'BAKERS BEND': '4470',
+    'CHARLEVILLE': '4470',
+    'GOWRIE STATION': '4470',
+    'LANGLO': '4470',
+    'MURWEH': '4470',
+    'RIVERSLEIGH': '4470',
+    'CLAVERTON': '4471',
+    'NARDOO SIDING': '4471',
+    'BLACKALL': '4472',
+    'MOUNT ENNISKILLEN': '4472',
+    'ADAVALE': '4474',
+    'CHEEPIE': '4475',
+    'AUGATHELLA': '4477',
+    'UPPER WARREGO': '4477',
+    'BAYRICK': '4478',
+    'CALDERVALE': '4478',
+    'LUMEAH': '4478',
+    'MACFARLANE': '4478',
+    'MINNIE DOWNS': '4478',
+    'SCRUBBY CREEK': '4478',
+    'TAMBO': '4478',
+    'YANDARLO': '4478',
+    'COOLADDI': '4479',
+    'EROMANGA': '4480',
+    'QUILPIE': '4480',
+    'FARRARS CREEK': '4481',
+    'TANBAR': '4481',
+    'WINDORAH': '4481',
+    'BIRDSVILLE': '4482',
+    'DIRRANBANDI': '4486',
+    'HEBEL': '4486',
+    'BEGONIA': '4487',
+    'BOLLON': '4488',
+    'NEBINE': '4488',
+    'WYANDRA': '4489',
+    'COONGOOLA': '4490',
+    'CUNNAMULLA': '4490',
+    'CUTTABURRA': '4490',
+    'HUMEBURN': '4490',
+    'JOBS GATE': '4490',
+    'NOORAMA': '4490',
+    'TUEN': '4490',
+    'WIDGEEGOARA': '4490',
+    'YOWAH': '4490',
+    'EULO': '4491',
+    'BULLAWARRA': '4492',
+    'BULLOO DOWNS': '4492',
+    'DYNEVOR': '4492',
+    'NOCKATUNGA': '4492',
+    'NORLEY': '4492',
+    'THARGOMINDAH': '4492',
+    'BUNGUNYA': '4494',
+    'NORTH BUNGUNYA': '4494',
+    'TARAWERA': '4494',
+    'NORTH TALWOOD': '4496',
+    'SOUTH TALWOOD': '4496',
+    'TALWOOD': '4496',
+    'DAYMAR': '4497',
+    'THALLON': '4497',
+    'WEENGALLON': '4497',
+    'KIOMA': '4498',
+    'TOOBEAH': '4498',
+    'BRENDALE': '4500',
+    'BRENDALE BC': '4500',
+    'BRENDALE DC': '4500',
+    'CASHMERE': '4500',
+    'CLEAR MOUNTAIN': '4500',
+    'JOYNER': '4500',
+    'STRATHPINE': '4500',
+    'STRATHPINE CENTRE': '4500',
+    'WARNER': '4500',
+    'LAWNTON': '4501',
+    'PETRIE': '4502',
+    'DAKABIN': '4503',
+    'GRIFFIN': '4503',
+    'KALLANGUR': '4503',
+    'KURWONGBAH': '4503',
+    'MURRUMBA DOWNS': '4503',
+    'WHITESIDE': '4503',
+    'NARANGBA': '4504',
+    'BURPENGARY': '4505',
+    'BURPENGARY DC': '4505',
+    'MOORINA': '4506',
+    'MORAYFIELD': '4506',
+    'BANKSIA BEACH': '4507',
+    'BELLARA': '4507',
+    'BONGAREE': '4507',
+    'BRIBIE ISLAND': '4507',
+    'BRIBIE ISLAND NORTH': '4507',
+    'WELSBY': '4507',
+    'WHITE PATCH': '4507',
+    'WOORIM': '4507',
+    'DECEPTION BAY': '4508',
+    'MANGO HILL': '4509',
+    'NORTH LAKES': '4509',
+    'BEACHMERE': '4510',
+    'BELLMERE': '4510',
+    'CABOOLTURE': '4510',
+    'CABOOLTURE BC': '4510',
+    'CABOOLTURE SOUTH': '4510',
+    'MELDALE': '4510',
+    'MOODLU': '4510',
+    'ROCKSBERG': '4510',
+    'TOORBUL': '4510',
+    'UPPER CABOOLTURE': '4510',
+    'GODWIN BEACH': '4511',
+    'NINGI': '4511',
+    'SANDSTONE POINT': '4511',
+    'BRACALBA': '4512',
+    'WAMURAN': '4512',
+    'WAMURAN BASIN': '4512',
+    'BELLTHORPE': '4514',
+    'CEDARTON': '4514',
+    'COMMISSIONERS FLAT': '4514',
+    "D'AGUILAR": '4514',
+    'DELANEYS CREEK': '4514',
+    'MOUNT ARCHER': '4514',
+    'MOUNT DELANEY': '4514',
+    'NEURUM': '4514',
+    'VILLENEUVE': '4514',
+    'GLENFERN': '4515',
+    'HAZELDEAN': '4515',
+    'JIMNA': '4515',
+    'KILCOY': '4515',
+    'KINGAHAM': '4515',
+    'MONSILDALE': '4515',
+    'MOUNT KILCOY': '4515',
+    'ROYSTON': '4515',
+    'SHEEP STATION CREEK': '4515',
+    'WINYA': '4515',
+    'WOOLMAR': '4515',
+    'ELIMBAH': '4516',
+    'BEERBURRUM': '4517',
+    'GLASS HOUSE MOUNTAINS': '4518',
+    'BEERWAH': '4519',
+    'COOCHIN CREEK': '4519',
+    'CROHAMHURST': '4519',
+    'PEACHESTER': '4519',
+    'ARMSTRONG CREEK': '4520',
+    'CAMP MOUNTAIN': '4520',
+    'CLOSEBURN': '4520',
+    'DRAPER': '4520',
+    'ENOGGERA RESERVOIR': '4520',
+    'HIGHVALE': '4520',
+    'JOLLYS LOOKOUT': '4520',
+    'KOBBLE CREEK': '4520',
+    'MOUNT GLORIOUS': '4520',
+    'MOUNT NEBO': '4520',
+    'MOUNT SAMSON': '4520',
+    'SAMFORD': '4520',
+    'SAMFORD VALLEY': '4520',
+    'SAMFORD VILLAGE': '4520',
+    'SAMSONVALE': '4520',
+    'WIGHTS MOUNTAIN': '4520',
+    'YUGAR': '4520',
+    'CAMPBELLS POCKET': '4521',
+    'DAYBORO': '4521',
+    'KING SCRUB': '4521',
+    'LACEYS CREEK': '4521',
+    'MOUNT MEE': '4521',
+    'OCEAN VIEW': '4521',
+    'RUSH CREEK': '4521',
+    'MOUNT MELLUM': '4550',
+    'AROONA': '4551',
+    'BATTERY HILL': '4551',
+    'BELLS CREEK': '4551',
+    'CALOUNDRA': '4551',
+    'CALOUNDRA BC': '4551',
+    'CALOUNDRA DC': '4551',
+    'CALOUNDRA WEST': '4551',
+    'CURRIMUNDI': '4551',
+    'DICKY BEACH': '4551',
+    'KINGS BEACH': '4551',
+    'LITTLE MOUNTAIN': '4551',
+    'MERIDAN PLAINS': '4551',
+    'MOFFAT BEACH': '4551',
+    'PELICAN WATERS': '4551',
+    'BALD KNOB': '4552',
+    'BALMORAL RIDGE': '4552',
+    'BOOROOBIN': '4552',
+    'CAMBROON': '4552',
+    'CONONDALE': '4552',
+    'CRYSTAL WATERS': '4552',
+    'ELAMAN CREEK': '4552',
+    'HARPER CREEK': '4552',
+    'MALENY': '4552',
+    'NORTH MALENY': '4552',
+    'REESVILLE': '4552',
+    'WITTA': '4552',
+    'WOOTHA': '4552',
+    'DIAMOND VALLEY': '4553',
+    'GLENVIEW': '4553',
+    'MOOLOOLAH': '4553',
+    'MOOLOOLAH VALLEY': '4553',
+    'PALMVIEW': '4553',
+    'EUDLO': '4554',
+    'ILKLEY': '4554',
+    'CHEVALLUM': '4555',
+    'HUNCHY': '4555',
+    'LANDERS SHOOT': '4555',
+    'BUDERIM': '4556',
+    'KUNDA PARK': '4556',
+    'MONS': '4556',
+    'SIPPY DOWNS': '4556',
+    'TANAWHA': '4556',
+    'MOOLOOLABA': '4557',
+    'COTTON TREE': '4558',
+    'KULUIN': '4558',
+    'MAROOCHYDORE': '4558',
+    'MAROOCHYDORE BC': '4558',
+    'MAROOCHYDORE DC': '4558',
+    'SUNSHINE PLAZA': '4558',
+    'DIDDILLIBAH': '4559',
+    'KIELS MOUNTAIN': '4559',
+    'WEST WOOMBYE': '4559',
+    'WOOMBYE': '4559',
+    'BLI BLI': '4560',
+    'COES CREEK': '4560',
+    'COOLOOLABIN': '4560',
+    'DULONG': '4560',
+    'FLAXTON': '4560',
+    'HIGHWORTH': '4560',
+    'IMAGE FLAT': '4560',
+    'KIAMBA': '4560',
+    'KULANGOOR': '4560',
+    'KUREELPA': '4560',
+    'MAPLETON': '4560',
+    'MONTVILLE': '4560',
+    'NAMBOUR': '4560',
+    'NAMBOUR BC': '4560',
+    'NAMBOUR DC': '4560',
+    'NAMBOUR WEST': '4560',
+    'PARKLANDS': '4560',
+    'PERWILLOWEN': '4560',
+    'ROSEMOUNT': '4560',
+    'SUNSHINE COAST MC': '4560',
+    'TOWEN MOUNTAIN': '4560',
+    'BRIDGES': '4561',
+    'MAROOCHY RIVER': '4561',
+    'NINDERRY': '4561',
+    'VALDORA': '4561',
+    'YANDINA': '4561',
+    'YANDINA CREEK': '4561',
+    'BELLI PARK': '4562',
+    'DOONAN': '4562',
+    'EERWAH VALE': '4562',
+    'EUMUNDI': '4562',
+    'VERRIERDALE': '4562',
+    'WEYBA DOWNS': '4562',
+    'CARTERS RIDGE': '4563',
+    'COOROY': '4563',
+    'COOROY MOUNTAIN': '4563',
+    'LAKE MACDONALD': '4563',
+    'RIDGEWOOD': '4563',
+    'TINBEERWAH': '4563',
+    'MARCOOLA': '4564',
+    'MUDJIMBA': '4564',
+    'PACIFIC PARADISE': '4564',
+    'TWIN WATERS': '4564',
+    'BOREEN POINT': '4565',
+    'COOROIBAH': '4565',
+    'COOTHARABA': '4565',
+    'NOOSA NORTH SHORE': '4565',
+    'RINGTAIL CREEK': '4565',
+    'TEWANTIN': '4565',
+    'NOOSAVILLE': '4566',
+    'NOOSAVILLE BC': '4566',
+    'NOOSAVILLE DC': '4566',
+    'CASTAWAYS BEACH': '4567',
+    'NOOSA HEADS': '4567',
+    'SUNRISE BEACH': '4567',
+    'SUNSHINE BEACH': '4567',
+    'PINBARREN': '4568',
+    'COORAN': '4569',
+    'AMAMOOR': '4570',
+    'AMAMOOR CREEK': '4570',
+    'ANDERLEIGH': '4570',
+    'BANKS POCKET': '4570',
+    'BEENAAM VALLEY': '4570',
+    'BELLA CREEK': '4570',
+    'BELLS BRIDGE': '4570',
+    'BOLLIER': '4570',
+    'BROOLOO': '4570',
+    'CALGOA': '4570',
+    'CALICO CREEK': '4570',
+    'CANINA': '4570',
+    'CEDAR POCKET': '4570',
+    'COLES CREEK': '4570',
+    'COONDOO': '4570',
+    'CORELLA': '4570',
+    'CURRA': '4570',
+    'DAGUN': '4570',
+    'DOWNSFIELD': '4570',
+    'EAST DEEP CREEK': '4570',
+    'FISHERMANS POCKET': '4570',
+    'GILLDORA': '4570',
+    'GLASTONBURY': '4570',
+    'GLEN ECHO': '4570',
+    'GOOMBOORIAN': '4570',
+    'GUNALDA': '4570',
+    'GYMPIE': '4570',
+    'GYMPIE DC': '4570',
+    'IMBIL': '4570',
+    'JONES HILL': '4570',
+    'KANDANGA': '4570',
+    'KANDANGA CREEK': '4570',
+    'KANIGAN': '4570',
+    'KYBONG': '4570',
+    'LAGOON POCKET': '4570',
+    'LAKE BORUMBA': '4570',
+    'LANGSHAW': '4570',
+    'LOWER WONGA': '4570',
+    'MARODIAN': '4570',
+    'MARYS CREEK': '4570',
+    'MCINTOSH CREEK': '4570',
+    'MELAWONDI': '4570',
+    'MIVA': '4570',
+    'MONKLAND': '4570',
+    'MOOLOO': '4570',
+    'MOTHAR MOUNTAIN': '4570',
+    'MUNNA CREEK': '4570',
+    'NAHRUNDA': '4570',
+    'NEERDIE': '4570',
+    'NEUSA VALE': '4570',
+    'NORTH DEEP CREEK': '4570',
+    'PIE CREEK': '4570',
+    'SCOTCHY POCKET': '4570',
+    'SEXTON': '4570',
+    'SOUTHSIDE': '4570',
+    'TAMAREE': '4570',
+    'TANDUR': '4570',
+    'THE DAWN': '4570',
+    'THE PALMS': '4570',
+    'THEEBINE': '4570',
+    'TOOLARA FOREST': '4570',
+    'TRAVESTON': '4570',
+    'TUCHEKOI': '4570',
+    'TWO MILE': '4570',
+    'UPPER GLASTONBURY': '4570',
+    'UPPER KANDANGA': '4570',
+    'VETERAN': '4570',
+    'VICTORY HEIGHTS': '4570',
+    'WALLU': '4570',
+    'WIDGEE': '4570',
+    'WIDGEE CROSSING NORTH': '4570',
+    'WIDGEE CROSSING SOUTH': '4570',
+    'WILSONS POCKET': '4570',
+    'WOLVI': '4570',
+    'WOOLOOGA': '4570',
+    'WOONDUM': '4570',
+    'KIN KIN': '4571',
+    'ALEXANDRA HEADLAND': '4572',
+    'COOLUM BEACH': '4573',
+    'MARCUS BEACH': '4573',
+    'MOUNT COOLUM': '4573',
+    'PEREGIAN BEACH': '4573',
+    'PEREGIAN BEACH SOUTH': '4573',
+    'PEREGIAN SPRINGS': '4573',
+    'POINT ARKWRIGHT': '4573',
+    'YAROOMBA': '4573',
+    'COOLABINE': '4574',
+    'GHEERULLA': '4574',
+    'KENILWORTH': '4574',
+    'KIDAMAN CREEK': '4574',
+    'MOY POCKET': '4574',
+    'OBI OBI': '4574',
+    'BIRTINYA': '4575',
+    'BOKARINA': '4575',
+    'BUDDINA': '4575',
+    'MINYAMA': '4575',
+    'PARREARRA': '4575',
+    'WARANA': '4575',
+    'WURTULLA': '4575',
+    'COOLOOLA': '4580',
+    'COOLOOLA COVE': '4580',
+    'TIN CAN BAY': '4580',
+    'EURONG': '4581',
+    'FRASER ISLAND': '4581',
+    'INSKIP': '4581',
+    'ORCHID BEACH': '4581',
+    'RAINBOW BEACH': '4581',
+    'BLACK SNAKE': '4600',
+    'CINNABAR': '4600',
+    'KILKIVAN': '4600',
+    'MUDLO': '4600',
+    'OAKVIEW': '4600',
+    'BARAMBAH': '4601',
+    'BOONARA': '4601',
+    'BOOUBYJAN': '4601',
+    'GOOMERI': '4601',
+    'GOOMERIBONG': '4601',
+    'KINBOMBI': '4601',
+    'MANUMBAR': '4601',
+    'TANSEY': '4601',
+    'WRATTENS FOREST': '4601',
+    'BARLIL': '4605',
+    'BYEE': '4605',
+    'CHERBOURG': '4605',
+    'CLOYNA': '4605',
+    'COBBS HILL': '4605',
+    'CROWNTHORPE': '4605',
+    'KITOBA': '4605',
+    'MANYUNG': '4605',
+    'MERLWOOD': '4605',
+    'MOFFATDALE': '4605',
+    'MOONDOONER': '4605',
+    'MURGON': '4605',
+    'REDGATE': '4605',
+    'SILVERLEAF': '4605',
+    'SUNNY NOOK': '4605',
+    'WARNUNG': '4605',
+    'WOOROONDEN': '4605',
+    'CHELMSFORD': '4606',
+    'FAIRDALE': '4606',
+    'FICKS CROSSING': '4606',
+    'GREENVIEW': '4606',
+    'LEAFDALE': '4606',
+    'MOUNT MCEUEN': '4606',
+    'MP CREEK': '4606',
+    'WHEATLANDS': '4606',
+    'WONDAI': '4606',
+    'CUSHNIE': '4608',
+    'TINGOORA': '4608',
+    'WILKESDALE': '4608',
+    'WOOROOLIN': '4608',
+    'ALICE CREEK': '4610',
+    'BALLOGIE': '4610',
+    'BENAIR': '4610',
+    'BOOIE': '4610',
+    'BOONENNE': '4610',
+    'BOYNESIDE': '4610',
+    'CHAHPINGAH': '4610',
+    'COOLABUNIA': '4610',
+    'CRAWFORD': '4610',
+    'DANGORE': '4610',
+    'DURONG': '4610',
+    'DURONG SOUTH': '4610',
+    'ELLESMERE': '4610',
+    'GOODGER': '4610',
+    'GORDONBROOK': '4610',
+    'HALY CREEK': '4610',
+    'HODGLEIGH': '4610',
+    'INVERLAW': '4610',
+    'IRONPOT': '4610',
+    'KINGAROY': '4610',
+    'KINGAROY DC': '4610',
+    'KUMBIA': '4610',
+    'MANNUEM': '4610',
+    'MEMERAMBI': '4610',
+    'TAABINGA': '4610',
+    'TAABINGA VILLAGE': '4610',
+    'MARSHLANDS': '4611',
+    'MONDURE': '4611',
+    'HIVESVILLE': '4612',
+    'KAWL KAWL': '4612',
+    'KEYSLAND': '4612',
+    'STONELANDS': '4612',
+    'WIGTON': '4612',
+    'ABBEYWOOD': '4613',
+    'BOONDOOMA': '4613',
+    'BRIGOODA': '4613',
+    'COVERTY': '4613',
+    'KINLEYMORE': '4613',
+    'MELROSE': '4613',
+    'OKEDEN': '4613',
+    'PROSTON': '4613',
+    'SPEEDWELL': '4613',
+    'STALWORTH': '4613',
+    'NEUMGNA': '4614',
+    'UPPER YARRAMAN': '4614',
+    'BARKER CREEK FLAT': '4615',
+    'BULLCAMP': '4615',
+    'EAST NANANGO': '4615',
+    'ELGIN VALE': '4615',
+    'GLAN DEVON': '4615',
+    'JOHNSTOWN': '4615',
+    'KUNIOON': '4615',
+    'MAIDENWELL': '4615',
+    'NANANGO': '4615',
+    'PIMPIMBUDGEE': '4615',
+    'SANDY RIDGES': '4615',
+    'SOUTH EAST NANANGO': '4615',
+    'SOUTH NANANGO': '4615',
+    'TARONG': '4615',
+    'WATTLE CAMP': '4615',
+    'WENGENVILLE': '4615',
+    'WYALLA': '4615',
+    'ARAMARA': '4620',
+    'BROOWEENA': '4620',
+    'DOONGUL': '4620',
+    'GIGOOMGAN': '4620',
+    'GLENBAR': '4620',
+    'GUNGALOON': '4620',
+    'MALARGA': '4620',
+    'NORTH ARAMARA': '4620',
+    'TEEBAR': '4620',
+    'WOOCOO': '4620',
+    'BIGGENDEN': '4621',
+    'BOOMPA': '4621',
+    'COALSTOUN LAKES': '4621',
+    'CORINGA': '4621',
+    'DALLARNIL': '4621',
+    'DEGILBO': '4621',
+    'DIDCOT': '4621',
+    'GOLDEN FLEECE': '4621',
+    'LAKESIDE': '4621',
+    'WATERANGA': '4621',
+    'WOOWOONGA': '4621',
+    'ARANBANGA': '4625',
+    'BAN BAN': '4625',
+    'BAN BAN SPRINGS': '4625',
+    'BARLYNE': '4625',
+    'BINJOUR': '4625',
+    'BLAIRMORE': '4625',
+    'BON ACCORD': '4625',
+    'BRANCH CREEK': '4625',
+    'BYRNESTOWN': '4625',
+    'CAMPBELL CREEK': '4625',
+    'DIRNBIR': '4625',
+    'DUNDARRAH': '4625',
+    'GAYNDAH': '4625',
+    'GINOONDAN': '4625',
+    'GOOROOLBA': '4625',
+    'HARRIET': '4625',
+    'HUMPHERY': '4625',
+    'IDERAWAY': '4625',
+    'MINGO': '4625',
+    'MOUNT DEBATEABLE': '4625',
+    'MOUNT LAWLESS': '4625',
+    'PENWHAUPELL': '4625',
+    'PILE GULLY': '4625',
+    'REIDS CREEK': '4625',
+    'STOCKHAVEN': '4625',
+    'THE LIMITS': '4625',
+    'TOONDAHRA': '4625',
+    'WAHOON': '4625',
+    'WETHERON': '4625',
+    'WILSON VALLEY': '4625',
+    'WOODMILLAR': '4625',
+    'BEERON': '4626',
+    'BOYNEWOOD': '4626',
+    'BROVINIA': '4626',
+    'COONAMBULA': '4626',
+    'DERRI DERRA': '4626',
+    'DYKEHEAD': '4626',
+    'GLENRAE': '4626',
+    'GURGEENA': '4626',
+    'HAWKWOOD': '4626',
+    'MONOGORILBY': '4626',
+    'MUNDOWRAN': '4626',
+    'MUNDUBBERA': '4626',
+    "O'BIL BIL": '4626',
+    'OLD COORANGA': '4626',
+    'PHILPOTT': '4626',
+    'RIVERLEIGH': '4626',
+    'ABERCORN': '4627',
+    'CERATODUS': '4627',
+    'CYNTHIA': '4627',
+    'EIDSVOLD': '4627',
+    'EIDSVOLD EAST': '4627',
+    'EIDSVOLD WEST': '4627',
+    'GROSVENOR': '4627',
+    'MALMOE': '4627',
+    'WURUMA DAM': '4627',
+    'BANCROFT': '4630',
+    'BUKALI': '4630',
+    'CANIA': '4630',
+    'CANNINDAH': '4630',
+    'COOMINGLAH': '4630',
+    'COOMINGLAH FOREST': '4630',
+    'DALGA': '4630',
+    'GLENLEIGH': '4630',
+    'HARRAMI': '4630',
+    'KALPOWAR': '4630',
+    'KAPALDO': '4630',
+    'MONAL': '4630',
+    'MONTO': '4630',
+    'MOONFORD': '4630',
+    'MULGILDIE': '4630',
+    'MUNGUNGO': '4630',
+    'RAWBELLE': '4630',
+    'SELENE': '4630',
+    'SPLINTER CREEK': '4630',
+    'TELLEBANG': '4630',
+    'THREE MOON': '4630',
+    'YARROL': '4630',
+    'ALDERSHOT': '4650',
+    'ANTIGUA': '4650',
+    'BAUPLE': '4650',
+    'BAUPLE FOREST': '4650',
+    'BEAVER ROCK': '4650',
+    'BOONOOROO': '4650',
+    'BOONOOROO PLAINS': '4650',
+    'DUCKINWILLA': '4650',
+    'DUNDATHU': '4650',
+    'DUNMORA': '4650',
+    'FERNEY': '4650',
+    'GOOTCHIE': '4650',
+    'GRAHAMS CREEK': '4650',
+    'GUNDIAH': '4650',
+    'ISLAND PLANTATION': '4650',
+    'MAAROOM': '4650',
+    'MAGNOLIA': '4650',
+    'MARYBOROUGH DC': '4650',
+    'MARYBOROUGH WEST': '4650',
+    'MOUNT STEADMAN': '4650',
+    'MOUNT URAH': '4650',
+    'MUNGAR': '4650',
+    'OWANYILLA': '4650',
+    'PALLAS STREET MARYBOROUGH': '4650',
+    'PILERWA': '4650',
+    'PIONEERS REST': '4650',
+    'POONA': '4650',
+    'PRAWLE': '4650',
+    'ST MARY': '4650',
+    'TALEGALLA WEIR': '4650',
+    'TANDORA': '4650',
+    'TEDDINGTON': '4650',
+    'THE DIMONDS': '4650',
+    'THINOOMBA': '4650',
+    'TIARO': '4650',
+    'TINANA': '4650',
+    'TINANA SOUTH': '4650',
+    'TINNANBAR': '4650',
+    'TUAN': '4650',
+    'TUAN FOREST': '4650',
+    'WALKERS POINT': '4650',
+    'YENGARIE': '4650',
+    'YERRA': '4650',
+    'BUNYA CREEK': '4655',
+    'CRAIGNISH': '4655',
+    'DUNDOWRAN': '4655',
+    'DUNDOWRAN BEACH': '4655',
+    'ELI WATERS': '4655',
+    'GREAT SANDY STRAIT': '4655',
+    'HERVEY BAY': '4655',
+    'HERVEY BAY DC': '4655',
+    'KAWUNGAN': '4655',
+    'KINGFISHER BAY RESORT': '4655',
+    'NIKENBAH': '4655',
+    'PIALBA': '4655',
+    'POINT VERNON': '4655',
+    'RIVER HEADS': '4655',
+    'SCARNESS': '4655',
+    'SUNSHINE ACRES': '4655',
+    'SUSAN RIVER': '4655',
+    'TAKURA': '4655',
+    'TOOGOOM': '4655',
+    'URANGAN': '4655',
+    'URRAWEEN': '4655',
+    'WALLIEBUM': '4655',
+    'WALLIGAN': '4655',
+    'WONDUNNA': '4655',
+    'BEELBI CREEK': '4659',
+    'BURGOWAN': '4659',
+    'BURRUM': '4659',
+    'BURRUM HEADS': '4659',
+    'BURRUM RIVER': '4659',
+    'BURRUM TOWN': '4659',
+    'HOWARD': '4659',
+    'PACIFIC HAVEN': '4659',
+    'APPLE TREE CREEK': '4660',
+    'CHERWELL': '4660',
+    'CORDALBA': '4660',
+    'DOOLBI': '4660',
+    'FARNSFIELD': '4660',
+    'GOODWOOD': '4660',
+    'GREGORY RIVER': '4660',
+    'HORTON': '4660',
+    'ISIS CENTRAL': '4660',
+    'ISIS RIVER': '4660',
+    'KULLOGUM': '4660',
+    'NORTH GREGORY': '4660',
+    'NORTH ISIS': '4660',
+    'PROMISEDLAND': '4660',
+    'REDRIDGE': '4660',
+    'SOUTH ISIS': '4660',
+    'WOODGATE': '4660',
+    'TORBANLEA': '4662',
+    'ALLOWAY': '4670',
+    'AVENELL HEIGHTS': '4670',
+    'BARGARA': '4670',
+    'BRANYAN': '4670',
+    'BUNDABERG': '4670',
+    'BUNDABERG CENTRAL': '4670',
+    'BUNDABERG DC': '4670',
+    'BUNDABERG EAST': '4670',
+    'BUNDABERG NORTH': '4670',
+    'BUNDABERG SOUTH': '4670',
+    'BUNDABERG WEST': '4670',
+    'BURNETT HEADS': '4670',
+    'CALAVOS': '4670',
+    'COONARR': '4670',
+    'CORAL COVE': '4670',
+    'ELECTRA': '4670',
+    'ELLIOTT HEADS': '4670',
+    'FAIRYMEAD': '4670',
+    'GIVELDA': '4670',
+    'GOOBURRUM': '4670',
+    'INNES PARK': '4670',
+    'KALKIE': '4670',
+    'KEPNOCK': '4670',
+    'KINKUNA': '4670',
+    'MEADOWVALE': '4670',
+    'MON REPOS': '4670',
+    'MOORE PARK BEACH': '4670',
+    'MULLETT CREEK': '4670',
+    'NORVILLE': '4670',
+    'QUNABA': '4670',
+    'RUBYANNA': '4670',
+    'SHARON': '4670',
+    'SOUTH BINGERA': '4670',
+    'SOUTH KOLAN': '4670',
+    'SVENSSON HEIGHTS': '4670',
+    'THABEBAN': '4670',
+    'WALKERVALE': '4670',
+    'WATALGAN': '4670',
+    'WELCOME CREEK': '4670',
+    'WINFIELD': '4670',
+    'WOONGARRA': '4670',
+    'BOOLBOONDA': '4671',
+    'BOOYAL': '4671',
+    'BULLYARD': '4671',
+    'BUNGADOO': '4671',
+    'DALYSFORD': '4671',
+    'DAMASCUS': '4671',
+    'DELAN': '4671',
+    'DOUGHBOY': '4671',
+    'DRINAN': '4671',
+    'DUINGAL': '4671',
+    'GAETA': '4671',
+    'GOOD NIGHT': '4671',
+    'HORSE CAMP': '4671',
+    'KOLONGA': '4671',
+    'LAKE MONDURAN': '4671',
+    'MAROONDAN': '4671',
+    'MCILWRAITH': '4671',
+    'MOLANGUL': '4671',
+    'MONDURAN': '4671',
+    'MOOLBOOLAMAN': '4671',
+    'MORGANVILLE': '4671',
+    'MOUNT PERRY': '4671',
+    'MUNGY': '4671',
+    'NEARUM': '4671',
+    'NEW MOONTA': '4671',
+    'REDHILL FARMS': '4671',
+    'SKYRING RESERVE': '4671',
+    'ST AGNES': '4671',
+    'TAKILBERAN': '4671',
+    'TIRROAN': '4671',
+    'WALLAVILLE': '4671',
+    'WONBAH': '4671',
+    'WONBAH FOREST': '4671',
+    'MIARA': '4673',
+    'YANDARAN': '4673',
+    'BAFFLE CREEK': '4674',
+    'BERAJONDO': '4674',
+    'EULEILAH': '4674',
+    'MOUNT MARIA': '4674',
+    'OYSTER CREEK': '4674',
+    'RULES BEACH': '4674',
+    'TAUNTON': '4674',
+    'GINDORAN': '4676',
+    'LOWMEAD': '4676',
+    'AGNES WATER': '4677',
+    'CAPTAIN CREEK': '4677',
+    'COLOSSEUM': '4677',
+    'EURIMBULA': '4677',
+    'MIRIAM VALE': '4677',
+    'MOUNT TOM': '4677',
+    'ROUND HILL': '4677',
+    'SEVENTEEN SEVENTY': '4677',
+    'BOROREN': '4678',
+    'FORESHORES': '4678',
+    'RODDS BAY': '4678',
+    'TURKEY BEACH': '4678',
+    'BARNEY POINT': '4680',
+    'BEECHER': '4680',
+    'BENARABY': '4680',
+    'BOYNE ISLAND': '4680',
+    'BOYNE VALLEY': '4680',
+    'BOYNEDALE': '4680',
+    'BUILYAN': '4680',
+    'BURUA': '4680',
+    'BYELLEE': '4680',
+    'CALLEMONDAH': '4680',
+    'CLINTON': '4680',
+    'CURTIS ISLAND': '4680',
+    'DIGLUM': '4680',
+    'GLADSTONE BC': '4680',
+    'GLADSTONE DC': '4680',
+    'GLADSTONE SOUTH': '4680',
+    'GLADSTONECITY': '4680',
+    'GLEN EDEN': '4680',
+    'HERON ISLAND': '4680',
+    'IVERAGH': '4680',
+    'KIN KORA': '4680',
+    'KIRKWOOD': '4680',
+    'MANY PEAKS': '4680',
+    'MOUNT ALMA': '4680',
+    'NAGOORIN': '4680',
+    'NEW AUCKLAND': '4680',
+    'RIVER RANCH': '4680',
+    'SOUTH END': '4680',
+    'SOUTH GLADSTONE': '4680',
+    'SOUTH TREES': '4680',
+    'TANNUM SANDS': '4680',
+    'TARAGOOLA': '4680',
+    'TELINA': '4680',
+    'TOOLOOA': '4680',
+    'UBOBO': '4680',
+    'WEST GLADSTONE': '4680',
+    'WEST STOWE': '4680',
+    'WOODERSON': '4680',
+    'WURDONG HEIGHTS': '4680',
+    'ALDOGA': '4694',
+    'TARGINIE': '4694',
+    'YARWUN': '4694',
+    'AMBROSE': '4695',
+    'BRACEWELL': '4695',
+    'DARTS CREEK': '4695',
+    'EAST END': '4695',
+    'MACHINE CREEK': '4695',
+    'MOUNT LARCOM': '4695',
+    'BAJOOL': '4699',
+    'PORT ALMA': '4699',
+    'ALLENSTOWN': '4700',
+    'DEPOT HILL': '4700',
+    'FAIRY BOWER': '4700',
+    'GREAT KEPPEL ISLAND': '4700',
+    'PORT CURTIS': '4700',
+    'ROCKHAMPTON': '4700',
+    'ROCKHAMPTON CITY': '4700',
+    'ROCKHAMPTON HOSPITAL': '4700',
+    'THE KEPPELS': '4700',
+    'THE RANGE': '4700',
+    'WANDAL': '4700',
+    'WEST ROCKHAMPTON': '4700',
+    'BERSERKER': '4701',
+    'CENTRAL QUEENSLAND UNIVERSITY': '4701',
+    'FRENCHVILLE': '4701',
+    'GREENLAKE': '4701',
+    'KAWANA': '4701',
+    'KOONGAL': '4701',
+    'LAKES CREEK': '4701',
+    'LIMESTONE CREEK': '4701',
+    'NANKIN': '4701',
+    'NERIMBERA': '4701',
+    'NORMAN GARDENS': '4701',
+    'NORTH ROCKHAMPTON': '4701',
+    'PARK AVENUE': '4701',
+    'RED HILL ROCKHAMPTON': '4701',
+    'ROCKHAMPTON DC': '4701',
+    'ROCKHAMPTON NORTH': '4701',
+    'ROCKYVIEW': '4701',
+    'THE COMMON': '4701',
+    'ALBERTA': '4702',
+    'ALSACE': '4702',
+    'ALTON DOWNS': '4702',
+    'BALCOMBA': '4702',
+    'BANANA': '4702',
+    'BARALABA': '4702',
+    'BARNARD': '4702',
+    'BINGEGANG': '4702',
+    'BLACKDOWN': '4702',
+    'BLUFF': '4702',
+    'BOOLBURRA': '4702',
+    'BOULDERCOMBE': '4702',
+    'BUSHLEY': '4702',
+    'CANAL CREEK': '4702',
+    'CANOONA': '4702',
+    'CAWARRAL': '4702',
+    'CENTRAL QUEENSLAND MC': '4702',
+    'COMET': '4702',
+    'CONSUELO': '4702',
+    'COOMOO': '4702',
+    'COOROOMAN': '4702',
+    'COORUMBENE': '4702',
+    'COOWONGA': '4702',
+    'DALMA': '4702',
+    'DINGO': '4702',
+    'DIXALEA': '4702',
+    'DULULU': '4702',
+    'DUMPY CREEK': '4702',
+    'ETNA CREEK': '4702',
+    'FERNLEES': '4702',
+    'GAINSFORD': '4702',
+    'GARNANT': '4702',
+    'GINDIE': '4702',
+    'GOGANGO': '4702',
+    'GOOMALLY': '4702',
+    'GOOVIGEN': '4702',
+    'GOOWARRA': '4702',
+    'GRACEMERE': '4702',
+    'JAMBIN': '4702',
+    'JARDINE': '4702',
+    'JELLINBAH': '4702',
+    'JOSKELEIGH': '4702',
+    'KABRA': '4702',
+    'KALAPA': '4702',
+    'KEPPEL SANDS': '4702',
+    'KOKOTUNGO': '4702',
+    'KUNWARARA': '4702',
+    'LOWESBY': '4702',
+    'MARMOR': '4702',
+    'MIDGEE': '4702',
+    'MILMAN': '4702',
+    'MOONMERA': '4702',
+    'MORINISH': '4702',
+    'MORINISH SOUTH': '4702',
+    'MOUNT CHALMERS': '4702',
+    'PARKHURST': '4702',
+    'PINK LILY': '4702',
+    'PLUM TREE': '4702',
+    'RIDGELANDS': '4702',
+    'ROLLESTON': '4702',
+    'ROSSMOYA': '4702',
+    'RUBYVALE': '4702',
+    'SHOALWATER': '4702',
+    'SMOKY CREEK': '4702',
+    'SOUTH YAAMBA': '4702',
+    'STANAGE': '4702',
+    'STANWELL': '4702',
+    'THE CAVES': '4702',
+    'THOMPSON POINT': '4702',
+    'TUNGAMULL': '4702',
+    'ULOGIE': '4702',
+    'WILLOWS': '4702',
+    'WILLOWS GEMFIELDS': '4702',
+    'WOOLEIN': '4702',
+    'WOOROONA': '4702',
+    'WOWAN': '4702',
+    'WYCARBAH': '4702',
+    'YARAKA': '4702',
+    'ADELAIDE PARK': '4703',
+    'BARLOWS HILL': '4703',
+    'BARMARYEE': '4703',
+    'BARMOYA': '4703',
+    'BONDOOLA': '4703',
+    'BUNGUNDARRA': '4703',
+    'BYFIELD': '4703',
+    'CAUSEWAY LAKE': '4703',
+    'COBRABALL': '4703',
+    'COOEE BAY': '4703',
+    'FARNBOROUGH': '4703',
+    'INVERNESS': '4703',
+    'KINKA BEACH': '4703',
+    'LAKE MARY': '4703',
+    'LAMMERMOOR': '4703',
+    'MEIKLEVILLE HILL': '4703',
+    'MULAMBIN': '4703',
+    'MULARA': '4703',
+    'PACIFIC HEIGHTS': '4703',
+    'ROSSLYN': '4703',
+    'TANBY': '4703',
+    'TARANGANBA': '4703',
+    'TAROOMBALL': '4703',
+    'WEERRIBA': '4703',
+    'WOODBURY': '4703',
+    'YEPPOON': '4703',
+    'WATTLEBANK': '4704',
+    'YAAMBA': '4704',
+    'CLARKE CREEK': '4705',
+    'LOTUS CREEK': '4705',
+    'MACKENZIE RIVER': '4705',
+    'MARLBOROUGH': '4705',
+    'MOUNT GARDINER': '4705',
+    'OGMORE': '4706',
+    'ST LAWRENCE': '4707',
+    'THE PERCY GROUP': '4707',
+    'TIERI': '4709',
+    'EMU PARK': '4710',
+    'ZILZIE': '4710',
+    'DUARINGA': '4712',
+    'WOORABINDA': '4713',
+    'BAREE': '4714',
+    'BOULDER CREEK': '4714',
+    'FLETCHER CREEK': '4714',
+    'HAMILTON CREEK': '4714',
+    'HORSE CREEK': '4714',
+    'JOHNSONS HILL': '4714',
+    'LEYDENS HILL': '4714',
+    'MOONGAN': '4714',
+    'MOUNT MORGAN': '4714',
+    'NINE MILE CREEK': '4714',
+    'OAKEY CREEK': '4714',
+    'STRUCK OIL': '4714',
+    'THE MINE': '4714',
+    'TROTTER CREEK': '4714',
+    'WALMUL': '4714',
+    'WALTERHALL': '4714',
+    'WURA': '4714',
+    'BILOELA': '4715',
+    'CALLIDE': '4715',
+    'DAKENBA': '4715',
+    'DUMGREE': '4715',
+    'GREYCLIFFE': '4715',
+    'MOUNT MURCHISON': '4715',
+    'ORANGE CREEK': '4715',
+    'TARRAMBA': '4715',
+    'VALENTINE PLAINS': '4715',
+    'LAWGI DAWES': '4716',
+    'THANGOOL': '4716',
+    'BLACKWATER': '4717',
+    'BAUHINIA': '4718',
+    'DROMEDARY': '4718',
+    'MOURA': '4718',
+    'MUNGABUNDA': '4718',
+    'OOMBABEER': '4718',
+    'RHYDDING': '4718',
+    'ROUNDSTONE': '4718',
+    'WARNOAH': '4718',
+    'CRACOW': '4719',
+    'GLENMORAL': '4719',
+    'ISLA': '4719',
+    'LONESOME CREEK': '4719',
+    'YAMALA': '4720',
+    'ARGYLL': '4721',
+    'CLERMONT': '4721',
+    'FRANKFIELD': '4721',
+    'GEMINI MOUNTAINS': '4721',
+    'KILCUMMIN': '4721',
+    'PASHA': '4721',
+    'WOLFANG': '4721',
+    'CAIRDBEIGN': '4722',
+    'CONA CREEK': '4722',
+    'NANDOWRIE': '4722',
+    'ORION': '4722',
+    'SPRINGSURE': '4722',
+    'WEALWANDANGIE': '4722',
+    'BELCONG': '4723',
+    'CAPELLA': '4723',
+    'CARBINE CREEK': '4723',
+    'CHIRNSIDE': '4723',
+    'CRINUM': '4723',
+    'HIBERNIA': '4723',
+    'KHOSH BULDUK': '4723',
+    'LOWESTOFF': '4723',
+    'MOUNT MACARTHUR': '4723',
+    'RETRO': '4723',
+    'ALPHA': '4724',
+    'DRUMMONDSLOPE': '4724',
+    'PINE HILL': '4724',
+    'PORT WINE': '4724',
+    'SEDGEFORD': '4724',
+    'SURBITON': '4724',
+    'BARCALDINE': '4725',
+    'BARCALDINE DOWNS': '4725',
+    'PATRICK': '4725',
+    'TARA STATION': '4725',
+    'ARAMAC': '4726',
+    'PELICAN CREEK': '4726',
+    'ILFRACOMBE': '4727',
+    'MEXICO': '4728',
+    'CAMOOLA': '4730',
+    'CHORREGON': '4730',
+    'ERNESTINA': '4730',
+    'MANEROO': '4730',
+    'MORELLA': '4730',
+    'VERGEMONT': '4730',
+    'ISISFORD': '4731',
+    'MUTTABURRA': '4732',
+    'TABLEDERRY': '4732',
+    'CORFIELD': '4733',
+    'DIAMANTINA LAKES': '4735',
+    'MIDDLETON': '4735',
+    'OPALTON': '4735',
+    'JUNDAH': '4736',
+    'ARMSTRONG BEACH': '4737',
+    'BLUE MOUNTAIN': '4737',
+    'FRESHWATER POINT': '4737',
+    'SARINA': '4737',
+    'SARINA BEACH': '4737',
+    'SARINA RANGE': '4737',
+    'ILBILBIE': '4738',
+    'KOUMALA': '4738',
+    'CARMILA': '4739',
+    'ALLIGATOR CREEK': '4740',
+    'ANDERGROVE': '4740',
+    'BALBERRA': '4740',
+    'BALNAGOWAN': '4740',
+    'BELMUNDA': '4740',
+    'BLACKS BEACH': '4740',
+    'CAPE HILLSBOROUGH': '4740',
+    'CHELONA': '4740',
+    'CONINGSBY': '4740',
+    'DOLPHIN HEADS': '4740',
+    'DUMBLETON': '4740',
+    'DUNDULA': '4740',
+    'DUNNROCK': '4740',
+    'EAST MACKAY': '4740',
+    'EIMEO': '4740',
+    'ERAKALA': '4740',
+    'FOULDEN': '4740',
+    'GLENELLA': '4740',
+    'GRASSTREE BEACH': '4740',
+    'HABANA': '4740',
+    'HALIDAY BAY': '4740',
+    'HAY POINT': '4740',
+    'MACKAY': '4740',
+    'MACKAY BC': '4740',
+    'MACKAY CANELAND': '4740',
+    'MACKAY DC': '4740',
+    'MACKAY HARBOUR': '4740',
+    'MACKAY NORTH': '4740',
+    'MACKAY SOUTH': '4740',
+    'MACKAY WEST': '4740',
+    'MCEWENS BEACH': '4740',
+    'MOUNT JUKES': '4740',
+    'MUNBURA': '4740',
+    'NINDAROO': '4740',
+    'NORTH MACKAY': '4740',
+    'OORALEA': '4740',
+    'PAGET': '4740',
+    'RACECOURSE': '4740',
+    'ROSELLA': '4740',
+    'RURAL VIEW': '4740',
+    'SANDIFORD': '4740',
+    'SLADE POINT': '4740',
+    'SOUTH MACKAY': '4740',
+    'TE KOWAI': '4740',
+    'THE LEAP': '4740',
+    'WEST MACKAY': '4740',
+    'BALL BAY': '4741',
+    'BRIGHTLY': '4741',
+    'CLAIRVIEW': '4741',
+    'DAYDREAM ISLAND': '4741',
+    'ETON': '4741',
+    'ETON NORTH': '4741',
+    'EUNGELLA HINTERLAND': '4741',
+    'FARLEIGH': '4741',
+    'GARGETT': '4741',
+    'HAMPDEN': '4741',
+    'HAZLEDEAN': '4741',
+    'HOOK ISLAND': '4741',
+    'KALARKA': '4741',
+    'KINCHANT DAM': '4741',
+    'KUTTABUL': '4741',
+    'LINDEMAN ISLAND': '4741',
+    'LONG ISLAND': '4741',
+    'MACKAY MC': '4741',
+    'MOUNT CHARLTON': '4741',
+    'MOUNT OSSA': '4741',
+    'MOUNT PELION': '4741',
+    'NORTH ETON': '4741',
+    'OAKENDEN': '4741',
+    'ORKABIE': '4741',
+    'OWENS CREEK': '4741',
+    'PLEYSTOWE': '4741',
+    'SOUTH MOLLE': '4741',
+    'YALBOROO': '4741',
+    'BURTON': '4742',
+    'EAGLEFIELD': '4742',
+    'HAIL CREEK': '4742',
+    'KEMMIS': '4742',
+    'MOUNT BRITTON': '4742',
+    'NEBO': '4742',
+    'OXFORD': '4742',
+    'TURRAWULLA': '4742',
+    'VALKYRIE': '4742',
+    'GLENDEN': '4743',
+    'SUTTOR': '4743',
+    'MORANBAH': '4744',
+    'MAY DOWNS': '4746',
+    'MIDDLEMOUNT': '4746',
+    'BUCASIA': '4750',
+    'SHOAL POINT': '4750',
+    'PALMYRA': '4751',
+    'VICTORIA PLAINS': '4751',
+    'WALKERSTON': '4751',
+    'DEVEREUX CREEK': '4753',
+    'MARIAN': '4753',
+    'BENHOLME': '4754',
+    'DOWS CREEK': '4754',
+    'MIRANI': '4754',
+    'MOUNT MARTIN': '4754',
+    'PINEVALE': '4754',
+    'SEPTIMUS': '4754',
+    'FINCH HATTON': '4756',
+    'NETHERDALE': '4756',
+    'BROKEN RIVER': '4757',
+    'CREDITON': '4757',
+    'DALRYMPLE HEIGHTS': '4757',
+    'EUNGELLA DAM': '4757',
+    'CALEN': '4798',
+    'MENTMORE': '4798',
+    'PINDI PINDI': '4798',
+    'ST HELENS BEACH': '4798',
+    'BLOOMSBURY': '4799',
+    'MIDGE POINT': '4799',
+    'ANDROMACHE': '4800',
+    'CANNON VALLEY': '4800',
+    'CAPE CONWAY': '4800',
+    'CAPE GLOUCESTER': '4800',
+    'CONWAY': '4800',
+    'CONWAY BEACH': '4800',
+    'CRYSTAL BROOK': '4800',
+    'DINGO BEACH': '4800',
+    'DITTMER': '4800',
+    'FOXDALE': '4800',
+    'GLEN ISLA': '4800',
+    'GOORGANGA CREEK': '4800',
+    'GOORGANGA PLAINS': '4800',
+    'GUNYARRA': '4800',
+    'HAMILTON PLAINS': '4800',
+    'HIDEAWAY BAY': '4800',
+    'KELSEY CREEK': '4800',
+    'LAGUNA QUAYS': '4800',
+    'LAKE PROSERPINE': '4800',
+    'LETHEBROOK': '4800',
+    'MOUNT JULIAN': '4800',
+    'MOUNT MARLOW': '4800',
+    'MOUNT PLUTO': '4800',
+    'MYRTLEVALE': '4800',
+    'PAULS POCKET': '4800',
+    'PROSERPINE': '4800',
+    'RIORDANVALE': '4800',
+    'SILVER CREEK': '4800',
+    'STRATHDICKIE': '4800',
+    'THOOPARA': '4800',
+    'WILSON BEACH': '4800',
+    'HAYMAN ISLAND': '4801',
+    'AIRLIE BEACH': '4802',
+    'CANNONVALE': '4802',
+    'FLAMETREE': '4802',
+    'JUBILEE POCKET': '4802',
+    'MANDALAY': '4802',
+    'MOUNT ROOPER': '4802',
+    'SHUTE HARBOUR': '4802',
+    'WHITSUNDAYS': '4802',
+    'WOODWARK': '4802',
+    'HAMILTON ISLAND': '4803',
+    'COLLINSVILLE': '4804',
+    'MOUNT COOLON': '4804',
+    'MOUNT WYATT': '4804',
+    'NEWLANDS': '4804',
+    'SPRINGLANDS': '4804',
+    'BINBEE': '4805',
+    'BOGIE': '4805',
+    'BOWEN': '4805',
+    'BRISK BAY': '4805',
+    'DELTA': '4805',
+    'GUMLU': '4805',
+    'GUTHALUNGRA': '4805',
+    'MERINDA': '4805',
+    'QUEENS BEACH': '4805',
+    'CARSTAIRS': '4806',
+    'FREDERICKSFIELD': '4806',
+    'HOME HILL': '4806',
+    'KIRKNIE': '4806',
+    'WUNJUNGA': '4806',
+    'AIRDMILLAN': '4807',
+    'AIRVILLE': '4807',
+    'ALVA': '4807',
+    'AYR': '4807',
+    'CLAREDALE': '4807',
+    'DALBEG': '4807',
+    'EIGHT MILE CREEK': '4807',
+    'JARVISFIELD': '4807',
+    'MCDESME': '4807',
+    'MILLAROO': '4807',
+    'MONA PARK': '4807',
+    'MOUNT KELLY': '4807',
+    'PARKSIDE': '4807',
+    'RITA ISLAND': '4807',
+    'SWANS LAGOON': '4807',
+    'BRANDON': '4808',
+    'COLEVALE': '4808',
+    'CROMARTY': '4809',
+    'GIRU': '4809',
+    'HORSESHOE LAGOON': '4809',
+    'JERONA': '4809',
+    'MOUNT SURROUND': '4809',
+    'SHIRBOURNE': '4809',
+    'UPPER HAUGHTON': '4809',
+    'BELGIAN GARDENS': '4810',
+    'CAPE CLEVELAND': '4810',
+    'NORTH WARD': '4810',
+    'PALLARENDA': '4810',
+    'RAILWAY ESTATE': '4810',
+    'ROWES BAY': '4810',
+    'SOUTH TOWNSVILLE': '4810',
+    'TOWN COMMON': '4810',
+    'TOWNSVILLE': '4810',
+    'TOWNSVILLE CITY': '4810',
+    'TOWNSVILLE DC': '4810',
+    'TOWNSVILLE MC': '4810',
+    'CLUDEN': '4811',
+    'IDALIA': '4811',
+    'JAMES COOK UNIVERSITY': '4811',
+    'MOUNT STUART': '4811',
+    'OAK VALLEY': '4811',
+    'OONOONBA': '4811',
+    'ROSENEATH': '4811',
+    'WULGURU': '4811',
+    'CURRAJONG': '4812',
+    'GULLIVER': '4812',
+    'HERMIT PARK': '4812',
+    'HYDE PARK': '4812',
+    'HYDE PARK CASTLETOWN': '4812',
+    'MUNDINGBURRA': '4812',
+    'MYSTERTON': '4812',
+    'ROSSLEA': '4812',
+    'AITKENVALE': '4814',
+    'AITKENVALE BC': '4814',
+    'CRANBROOK': '4814',
+    'GARBUTT': '4814',
+    'GARBUTT BC': '4814',
+    'GARBUTT EAST': '4814',
+    'HEATLEY': '4814',
+    'MOUNT LOUISA': '4814',
+    'MURRAY': '4814',
+    'THURINGOWA DC': '4814',
+    'VINCENT': '4814',
+    'CONDON': '4815',
+    'GRANITE VALE': '4815',
+    'GUMLOW': '4815',
+    'PINNACLES': '4815',
+    'RASMUSSEN': '4815',
+    'BALGAL BEACH': '4816',
+    'BARRINGHA': '4816',
+    'BROOKHILL': '4816',
+    'CALCIUM': '4816',
+    'CARRUCHAN': '4816',
+    'CLEMANT': '4816',
+    'CRIMEA': '4816',
+    'CUNGULLA': '4816',
+    'ELLERBECK': '4816',
+    'JULAGO': '4816',
+    'KENNEDY': '4816',
+    'MACROSSAN': '4816',
+    'MALPASTRENTON': '4816',
+    'MINGELA': '4816',
+    'MUTARNEE': '4816',
+    'NELIA': '4816',
+    'NOME': '4816',
+    'PALM ISLAND': '4816',
+    'PALUMA': '4816',
+    'PENTLAND': '4816',
+    'REID RIVER': '4816',
+    'ROLLINGSTONE': '4816',
+    'ROSS RIVER': '4816',
+    'SAVANNAH': '4816',
+    'SELLHEIM': '4816',
+    'THE CAPE': '4816',
+    'TOOMULLA': '4816',
+    'TOONPAN': '4816',
+    'TORRENS CREEK': '4816',
+    'ALICE RIVER': '4817',
+    'BOHLE PLAINS': '4817',
+    'HERVEY RANGE': '4817',
+    'KIRWAN': '4817',
+    'RANGEWOOD': '4817',
+    'THURINGOWA CENTRAL': '4817',
+    'THURINGOWA CENTRAL BC': '4817',
+    'BEACH HOLM': '4818',
+    'BLACK RIVER': '4818',
+    'BLUE HILLS': '4818',
+    'BLUEWATER': '4818',
+    'BLUEWATER PARK': '4818',
+    'BOHLE': '4818',
+    'BURDELL': '4818',
+    'BUSHLAND BEACH': '4818',
+    'DEERAGUN': '4818',
+    'JENSEN': '4818',
+    'LYNAM': '4818',
+    'MOUNT LOW': '4818',
+    'MOUNT ST JOHN': '4818',
+    'SAUNDERS BEACH': '4818',
+    'SHAW': '4818',
+    'TOOLAKEA': '4818',
+    'YABULU': '4818',
+    'FLORENCE BAY': '4819',
+    'HORSESHOE BAY': '4819',
+    'MAGNETIC ISLAND': '4819',
+    'NELLY BAY': '4819',
+    'PICNIC BAY': '4819',
+    'WEST POINT': '4819',
+    'ALABAMA HILL': '4820',
+    'BALFES CREEK': '4820',
+    'BLACK JACK': '4820',
+    'BREDDAN': '4820',
+    'CAMPASPE': '4820',
+    'CHARTERS TOWERS': '4820',
+    'COLUMBIA': '4820',
+    'DOTSWOOD': '4820',
+    'GRAND SECRET': '4820',
+    'LISSNER': '4820',
+    'MILLCHESTER': '4820',
+    'MOSMAN PARK': '4820',
+    'QUEENTON': '4820',
+    'SEVENTY MILE': '4820',
+    'TOLL': '4820',
+    'TOWERS HILL': '4820',
+    'DUTTON RIVER': '4821',
+    'HUGHENDEN': '4821',
+    'PORCUPINE': '4821',
+    'STAMFORD': '4821',
+    'TANGORIN': '4821',
+    'BURLEIGH': '4822',
+    'CAMBRIDGE': '4822',
+    'MAXWELTON': '4822',
+    'NONDA': '4822',
+    'SAXBY': '4822',
+    'VICTORIA VALE': '4822',
+    'WOOLGAR': '4822',
+    'CARPENTARIA': '4823',
+    'JULIA CREEK': '4823',
+    'KYNUNA': '4823',
+    'MCKINLAY': '4823',
+    'STOKES': '4823',
+    'TALDORA': '4823',
+    'CLONCURRY': '4824',
+    'FOUR WAYS': '4824',
+    'GIDYA': '4824',
+    'KURIDALA': '4824',
+    'OORINDI': '4824',
+    'THREE RIVERS': '4824',
+    'ALPURRURULAM': '4825',
+    'BREAKAWAY': '4825',
+    'BUCKINGHAM': '4825',
+    'CARRANDOTTA': '4825',
+    'DAJARRA': '4825',
+    'DUCHESS': '4825',
+    'FIELDING': '4825',
+    'GEORGINA': '4825',
+    'GUNPOWDER': '4825',
+    'HEALY': '4825',
+    'KALKADOON': '4825',
+    'LANSKEY': '4825',
+    'LAWN HILL': '4825',
+    'MENZIES': '4825',
+    'MICA CREEK': '4825',
+    'MILES END': '4825',
+    'MOUNT ISA': '4825',
+    'MOUNT ISA DC': '4825',
+    'MOUNT ISA EAST': '4825',
+    'PIONEER': '4825',
+    'PITURIE': '4825',
+    'RANKEN': '4825',
+    'SPREADBOROUGH': '4825',
+    'SUNSET': '4825',
+    'THE MONUMENT': '4825',
+    'TOWNVIEW': '4825',
+    'WINSTON': '4825',
+    'CAMOOWEAL': '4828',
+    'BEDOURIE': '4829',
+    'BOULIA': '4829',
+    'MIN MIN': '4829',
+    'STURT': '4829',
+    'TOKO': '4829',
+    'WARENDA': '4829',
+    'WILLS': '4829',
+    'BURKETOWN': '4830',
+    'DOOMADGEE': '4830',
+    'GREGORY DOWNS': '4830',
+    'CARDWELL': '4849',
+    'DAMPER CREEK': '4849',
+    'LUMHOLTZ': '4849',
+    'RUNGOO': '4849',
+    'ABERGOWRIE': '4850',
+    'ALLINGHAM': '4850',
+    'BAMBAROO': '4850',
+    'BEMERSIDE': '4850',
+    'BLACKROCK': '4850',
+    'BRAEMEADOWS': '4850',
+    'COOLBIE': '4850',
+    'CORDELIA': '4850',
+    'DALRYMPLE CREEK': '4850',
+    'FORESTHOME': '4850',
+    'FORREST BEACH': '4850',
+    'GAIRLOCH': '4850',
+    'GARRAWALT': '4850',
+    'HALIFAX': '4850',
+    'HAWKINS CREEK': '4850',
+    'HELENS HILL': '4850',
+    'INGHAM': '4850',
+    'LANNERCOST': '4850',
+    'LONG POCKET': '4850',
+    'LUCINDA': '4850',
+    'MACKNADE': '4850',
+    'MOUNT FOX': '4850',
+    'ORIENT': '4850',
+    'PEACOCK SIDING': '4850',
+    'TOOBANNA': '4850',
+    'TREBONNE': '4850',
+    'UPPER STONE': '4850',
+    'VALLEY OF LAGOONS': '4850',
+    'VICTORIA ESTATE': '4850',
+    'VICTORIA PLANTATION': '4850',
+    'WALLAMAN': '4850',
+    'WHARPS': '4850',
+    'YURUGA': '4850',
+    'BINGIL BAY': '4852',
+    'CARMOO': '4852',
+    'DJIRU': '4852',
+    'DUNK': '4852',
+    'GARNERS BEACH': '4852',
+    'MIDGEREE BAR': '4852',
+    'MISSION BEACH': '4852',
+    'SOUTH MISSION BEACH': '4852',
+    "TAM O'SHANTER": '4852',
+    'WONGALING BEACH': '4852',
+    'BILYANA': '4854',
+    'BIRKALLA': '4854',
+    'BULGUN': '4854',
+    'CARDSTONE': '4854',
+    'DINGO POCKET': '4854',
+    'DJARAWONG': '4854',
+    'EAST FELUGA': '4854',
+    'EURAMO': '4854',
+    'FELUGA': '4854',
+    'HULL HEADS': '4854',
+    'JARRA CREEK': '4854',
+    'KOOROOMOOL': '4854',
+    'LOWER TULLY': '4854',
+    'MERRYBURN': '4854',
+    'MIDGENOO': '4854',
+    'MOUNT MACKAY': '4854',
+    'MUNRO PLAINS': '4854',
+    'MURRAY UPPER': '4854',
+    'MURRIGAL': '4854',
+    'ROCKINGHAM': '4854',
+    'SILKY OAK': '4854',
+    'TULLY': '4854',
+    'TULLY HEADS': '4854',
+    'WALTER HILL': '4854',
+    'WARRAMI': '4854',
+    'DAVESON': '4855',
+    'EL ARISH': '4855',
+    'FRIDAY POCKET': '4855',
+    'GRANADILLA': '4855',
+    'JAFFA': '4855',
+    'MAADI': '4855',
+    'MARIA CREEKS': '4855',
+    'SHELL POCKET': '4855',
+    'GOOLBOO': '4856',
+    'JAPOONVALE': '4856',
+    'MCCUTCHEON': '4856',
+    'NO 4 BRANCH': '4856',
+    'NO 5 BRANCH': '4856',
+    'SILKWOOD': '4856',
+    'WALTER LEVER ESTATE': '4856',
+    'SILKWOOD EAST': '4857',
+    'COMOON LOOP': '4858',
+    'ETTY BAY': '4858',
+    'MARTYVILLE': '4858',
+    'MOURILYAN': '4858',
+    'MOURILYAN HARBOUR': '4858',
+    'NEW HARBOURLINE': '4858',
+    'NGATJAN': '4859',
+    'NO 6 BRANCH': '4859',
+    'SOUTH JOHNSTONE': '4859',
+    'BAMBOO CREEK': '4860',
+    'BELVEDERE': '4860',
+    'COCONUTS': '4860',
+    'COOROO LANDS': '4860',
+    'COORUMBA': '4860',
+    'COQUETTE POINT': '4860',
+    'CULLINANE': '4860',
+    'DARADGEE': '4860',
+    'EAST INNISFAIL': '4860',
+    'EAST PALMERSTON': '4860',
+    'EUBENANGEE': '4860',
+    'FITZGERALD CREEK': '4860',
+    'FLYING FISH POINT': '4860',
+    'GARRADUNGA': '4860',
+    'GOONDI': '4860',
+    'GOONDI BEND': '4860',
+    'GOONDI HILL': '4860',
+    'HUDSON': '4860',
+    'INNISFAIL': '4860',
+    'INNISFAIL ESTATE': '4860',
+    'JUBILEE HEIGHTS': '4860',
+    'MIGHELL': '4860',
+    'MUNDOO': '4860',
+    'NERADA': '4860',
+    'NJATJAN': '4860',
+    "O'BRIENS HILL": '4860',
+    'PIN GIN HILL': '4860',
+    'SOUTH INNISFAIL': '4860',
+    'STOTERS HILL': '4860',
+    'SUNDOWN': '4860',
+    'UPPER DARADGEE': '4860',
+    'VASA VIEWS': '4860',
+    'WANJURU': '4860',
+    'WEBB': '4860',
+    'WOOROONOORAN': '4860',
+    'BARTLE FRERE': '4861',
+    'EAST RUSSELL': '4861',
+    'GORDONVALE': '4865',
+    'GREEN HILL': '4865',
+    'KAMMA': '4865',
+    'LITTLE MULGRAVE': '4865',
+    'PACKERS CAMP': '4865',
+    'BAYVIEW HEIGHTS': '4868',
+    'MOUNT SHERIDAN': '4868',
+    'WOREE': '4868',
+    'BENTLEY PARK': '4869',
+    'EDMONTON': '4869',
+    'MOUNT PETER': '4869',
+    'AEROGLEN': '4870',
+    'BARRON GORGE': '4870',
+    'BRINSMEAD': '4870',
+    'BUNGALOW': '4870',
+    'CAIRNS': '4870',
+    'CAIRNS CENTRAL': '4870',
+    'CAIRNS CITY': '4870',
+    'CAIRNS DC': '4870',
+    'CAIRNS MC': '4870',
+    'CAIRNS NORTH': '4870',
+    'CAIRNS ORCHID PLAZA': '4870',
+    'EARLVILLE': '4870',
+    'EARLVILLE BC': '4870',
+    'EDGE HILL': '4870',
+    'KAMERUNGA': '4870',
+    'LAMB RANGE': '4870',
+    'MANOORA': '4870',
+    'MANUNDA': '4870',
+    'MARTYNVALE': '4870',
+    'MOOROOBOOL': '4870',
+    'NORTH CAIRNS': '4870',
+    'PARRAMATTA PARK': '4870',
+    'PORTSMITH': '4870',
+    'REDLYNCH': '4870',
+    'WESTCOURT': '4870',
+    'ABINGDON DOWNS': '4871',
+    'ALMADEN': '4871',
+    'ALOOMBA': '4871',
+    'AMBER': '4871',
+    'ARBOUIN': '4871',
+    'ARCHER RIVER': '4871',
+    'AURUKUN': '4871',
+    'BASILISK': '4871',
+    'BELLENDEN KER': '4871',
+    'BELLEVUE': '4871',
+    'BLACKBULL': '4871',
+    'BOMBEETA': '4871',
+    'BOOGAN': '4871',
+    'BRAMSTON BEACH': '4871',
+    'BULLERINGA': '4871',
+    'CAMP CREEK': '4871',
+    'CHILLAGOE': '4871',
+    'CLARAVILLE': '4871',
+    'COEN': '4871',
+    'CONJUBOY': '4871',
+    'CORALIE': '4871',
+    'COWLEY': '4871',
+    'COWLEY BEACH': '4871',
+    'COWLEY CREEK': '4871',
+    'CRYSTALBROOK': '4871',
+    'CURRAJAH': '4871',
+    'DEERAL': '4871',
+    'DESAILLY': '4871',
+    'EAST CREEK': '4871',
+    'EAST TRINITY': '4871',
+    'EDWARD RIVER': '4871',
+    'EINASLEIGH': '4871',
+    'ESMERALDA': '4871',
+    'FISHERY FALLS': '4871',
+    'FITZROY ISLAND': '4871',
+    'FORSAYTH': '4871',
+    'FOSSILBROOK': '4871',
+    'GAMBOOLA': '4871',
+    'GILBERT RIVER': '4871',
+    'GLEN BOUGHTON': '4871',
+    'GREEN ISLAND': '4871',
+    'GROGANVILLE': '4871',
+    'GUNUNA': '4871',
+    'HIGHBURY': '4871',
+    'HOLROYD RIVER': '4871',
+    'HURRICANE': '4871',
+    'JULATTEN': '4871',
+    'KARRON': '4871',
+    'KOWANYAMA': '4871',
+    'KURRIMINE BEACH': '4871',
+    'LAKEFIELD': '4871',
+    'LAKELAND': '4871',
+    'LAKELAND DOWNS': '4871',
+    'LAURA': '4871',
+    'LIZARD': '4871',
+    'LOWER COWLEY': '4871',
+    'LYNDSIDE': '4871',
+    'MACALISTER RANGE': '4871',
+    'MARAMIE': '4871',
+    'MENA CREEK': '4871',
+    'MIRIWINNI': '4871',
+    'MORESBY': '4871',
+    'MORNINGTON ISLAND': '4871',
+    'MOUNT CARBINE': '4871',
+    'MOUNT MOLLOY': '4871',
+    'MOUNT MULGRAVE': '4871',
+    'MOUNT MULLIGAN': '4871',
+    'MOUNT SURPRISE': '4871',
+    'NORTHHEAD': '4871',
+    'NYCHUM': '4871',
+    'PALMER': '4871',
+    'PETFORD': '4871',
+    'PORMPURAAW': '4871',
+    'PORTLAND ROADS': '4871',
+    'RED RIVER': '4871',
+    'SANDY POCKET': '4871',
+    'SOUTH WELLESLEY ISLANDS': '4871',
+    'SOUTHEDGE': '4871',
+    'STAATEN': '4871',
+    'TALAROO': '4871',
+    'THORNBOROUGH': '4871',
+    'UTCHEE CREEK': '4871',
+    'WANGAN': '4871',
+    'WARRUBULLEN': '4871',
+    'WAUGH POCKET': '4871',
+    'WELLESLEY ISLANDS': '4871',
+    'WEST WELLESLEY ISLANDS': '4871',
+    'WOOPEN CREEK': '4871',
+    'WROTHAM': '4871',
+    'YAGOONYA': '4871',
+    'YARRABAH': '4871',
+    'YARRADEN': '4871',
+    'BARRINE': '4872',
+    'BARWIDGI': '4872',
+    'DANBULLA': '4872',
+    'DIMBULAH': '4872',
+    'FORTY MILE': '4872',
+    'GLEN RUTH': '4872',
+    'GUNNAWARRA': '4872',
+    'INNOT HOT SPRINGS': '4872',
+    'KAIRI': '4872',
+    'KIRRAMA': '4872',
+    'KOOMBOOLOOMBA': '4872',
+    'KOWROWA': '4872',
+    'LAKE TINAROO': '4872',
+    'MINNAMOOLKA': '4872',
+    'MOUNT GARNET': '4872',
+    'MUNDERRA': '4872',
+    'MUTCHILBA': '4872',
+    'SILVER VALLEY': '4872',
+    'TINAROO': '4872',
+    'WAIRUNA': '4872',
+    'WALKAMIN': '4872',
+    'BAMBOO': '4873',
+    'CAPE TRIBULATION': '4873',
+    'CASSOWARY': '4873',
+    'COOYA BEACH': '4873',
+    'COW BAY': '4873',
+    'DAGMAR': '4873',
+    'DAINTREE': '4873',
+    'DEDIN': '4873',
+    'DIWAN': '4873',
+    'FINLAY VALE': '4873',
+    'FOREST CREEK': '4873',
+    'KIMBERLEY': '4873',
+    'LOW ISLES': '4873',
+    'LOWER DAINTREE': '4873',
+    'MIALLO': '4873',
+    'MOSSMAN': '4873',
+    'MOSSMAN GORGE': '4873',
+    'NEWELL': '4873',
+    'NOAH': '4873',
+    'SPURGEON': '4873',
+    'STEWART CREEK VALLEY': '4873',
+    'SYNDICATE': '4873',
+    'THORNTON BEACH': '4873',
+    'UPPER DAINTREE': '4873',
+    'WHYANBEEL': '4873',
+    'EVANS LANDING': '4874',
+    'JARDINE RIVER': '4874',
+    'MAPOON': '4874',
+    'MISSION RIVER': '4874',
+    'NANUM': '4874',
+    'NAPRANUM': '4874',
+    'SHELBURNE': '4874',
+    'TRUNDING': '4874',
+    'WEIPA': '4874',
+    'WEIPA AIRPORT': '4874',
+    'WENLOCK': '4874',
+    'BADU ISLAND': '4875',
+    'BANKS ISLAND': '4875',
+    'BOIGU ISLAND': '4875',
+    'COCONUT ISLAND': '4875',
+    'DARNLEY ISLAND': '4875',
+    'DAUAN ISLAND': '4875',
+    'ERUB': '4875',
+    'HORN ISLAND': '4875',
+    'JERVIS ISLAND': '4875',
+    'KUBIN VILLAGE': '4875',
+    'MABUIAG ISLAND': '4875',
+    'MOA ISLAND': '4875',
+    'MULGRAVE ISLAND': '4875',
+    'MURRAY ISLAND': '4875',
+    'SAIBAI ISLAND': '4875',
+    'STEPHENS ISLAND': '4875',
+    'TALBOT ISLAND': '4875',
+    'THURSDAY ISLAND': '4875',
+    'WARRABER ISLAND': '4875',
+    'YAM ISLAND': '4875',
+    'YORKE ISLAND': '4875',
+    'BAMAGA': '4876',
+    'INJINOO': '4876',
+    'NEW MAPOON': '4876',
+    'SEISIA': '4876',
+    'UMAGICO': '4876',
+    'CRAIGLIE': '4877',
+    'KILLALOE': '4877',
+    'MOWBRAY': '4877',
+    'OAK BEACH': '4877',
+    'PORT DOUGLAS': '4877',
+    'WANGETTI': '4877',
+    'BARRON': '4878',
+    'CARAVONICA': '4878',
+    'HOLLOWAYS BEACH': '4878',
+    'MACHANS BEACH': '4878',
+    'YORKEYS KNOB': '4878',
+    'CLIFTON BEACH': '4879',
+    'ELLIS BEACH': '4879',
+    'KEWARRA BEACH': '4879',
+    'PALM COVE': '4879',
+    'TRINITY BEACH': '4879',
+    'TRINITY PARK': '4879',
+    'ARRIGA': '4880',
+    'BIBOOHRA': '4880',
+    'CHEWKO': '4880',
+    'GLEN RUSSELL': '4880',
+    'MAREEBA': '4880',
+    'PADDYS GREEN': '4880',
+    'KOAH': '4881',
+    'KURANDA': '4881',
+    'MONA MONA': '4881',
+    'SPEEWAH': '4881',
+    'TOLGA': '4882',
+    'ATHERTON': '4883',
+    'EAST BARRON': '4883',
+    'UPPER BARRON': '4883',
+    'WONGABEL': '4883',
+    'GADGARRA': '4884',
+    'LAKE BARRINE': '4884',
+    'LAKE EACHAM': '4884',
+    'YUNGABURRA': '4884',
+    'BUTCHERS CREEK': '4885',
+    'GLEN ALLYN': '4885',
+    'JAGGAN': '4885',
+    'KUREEN': '4885',
+    'MALANDA': '4885',
+    'NORTH JOHNSTONE': '4885',
+    'PEERAMON': '4885',
+    'TARZALI': '4885',
+    'TOPAZ': '4885',
+    'BEATRICE': '4886',
+    'ELLINJAA': '4886',
+    'MAALAN': '4886',
+    'MIDDLEBROOK': '4886',
+    'MILLAA MILLAA': '4886',
+    'MINBUN': '4886',
+    'MOREGATTA': '4886',
+    'MUNGALLI': '4886',
+    'HERBERTON': '4887',
+    'IRVINEBANK': '4887',
+    'KALUNGA': '4887',
+    'MOOMIN': '4887',
+    'WATSONVILLE': '4887',
+    'WONDECLA': '4887',
+    'EVELYN': '4888',
+    'KABAN': '4888',
+    'MILLSTREAM': '4888',
+    'RAVENSHOE': '4888',
+    'TUMOULIN': '4888',
+    'HOWITT': '4890',
+    'NORMAN': '4890',
+    'NORMANTON': '4890',
+    'KARUMBA': '4891',
+    'AYTON': '4895',
+    'COOKTOWN': '4895',
+    'DEGARRA': '4895',
+    'ENDEAVOUR FALLS': '4895',
+    'HELENVALE': '4895',
+    'HOPE VALE': '4895',
+    'ROSSVILLE': '4895',
+    'STARCKE': '4895',
+    'WUJAL WUJAL': '4895',
+    'ADELAIDE': '5000',
+    'ADELAIDE BC': '5000',
+    'CITY WEST CAMPUS': '5000',
+    'HALIFAX STREET': '5000',
+    'HUTT STREET': '5000',
+    'RUNDLE MALL': '5000',
+    'STATION ARCADE': '5000',
+    'STURT STREET': '5000',
+    'NORTH ADELAIDE': '5006',
+    'NORTH ADELAIDE MELBOURNE ST': '5006',
+    'BOWDEN': '5007',
+    'BROMPTON': '5007',
+    'HINDMARSH': '5007',
+    'WELLAND': '5007',
+    'WEST HINDMARSH': '5007',
+    'CROYDON PARK SOUTH': '5008',
+    'DUDLEY PARK': '5008',
+    'RENOWN PARK': '5008',
+    'RIDLEYTON': '5008',
+    'WEST CROYDON': '5008',
+    'ALLENBY GARDENS': '5009',
+    'BEVERLEY': '5009',
+    'KILKENNY': '5009',
+    'ANGLE PARK': '5010',
+    'FERRYDEN PARK': '5010',
+    'REGENCY PARK': '5010',
+    'REGENCY PARK BC': '5010',
+    'WOODVILLE PARK': '5011',
+    'WOODVILLE SOUTH': '5011',
+    'WOODVILLE WEST': '5011',
+    'ATHOL PARK': '5012',
+    'MANSFIELD PARK': '5012',
+    'WOODVILLE GARDENS': '5012',
+    'WOODVILLE NORTH': '5012',
+    'GILLMAN': '5013',
+    'OTTOWAY': '5013',
+    'PENNINGTON': '5013',
+    'ROSEWATER': '5013',
+    'ROSEWATER EAST': '5013',
+    'WINGFIELD': '5013',
+    'QUEENSTOWN': '5014',
+    'ROYAL PARK': '5014',
+    'BIRKENHEAD': '5015',
+    'ETHELTON': '5015',
+    'GLANVILLE': '5015',
+    'NEW PORT': '5015',
+    'PORT ADELAIDE': '5015',
+    'PORT ADELAIDE BC': '5015',
+    'PORT ADELAIDE DC': '5015',
+    'LARGS BAY': '5016',
+    'LARGS NORTH': '5016',
+    'PETERHEAD': '5016',
+    'TAPEROO': '5017',
+    'OUTER HARBOR': '5018',
+    'SEMAPHORE': '5019',
+    'SEMAPHORE PARK': '5019',
+    'SEMAPHORE SOUTH': '5019',
+    'WEST LAKES SHORE': '5020',
+    'WEST LAKES': '5021',
+    'HENLEY BEACH': '5022',
+    'HENLEY BEACH SOUTH': '5022',
+    'KIRKCALDY': '5022',
+    'FINDON': '5023',
+    'SEATON NORTH': '5023',
+    'FULHAM GARDENS': '5024',
+    'WEST BEACH': '5024',
+    'FLINDERS PARK': '5025',
+    'KIDMAN PARK': '5025',
+    'MILE END': '5031',
+    'MILE END SOUTH': '5031',
+    'THEBARTON': '5031',
+    'TORRENSVILLE': '5031',
+    'TORRENSVILLE PLAZA': '5031',
+    'BROOKLYN PARK': '5032',
+    'LOCKLEYS': '5032',
+    'UNDERDALE': '5032',
+    'COWANDILLA': '5033',
+    'HILTON': '5033',
+    'HILTON PLAZA': '5033',
+    'MARLESTON': '5033',
+    'MARLESTON DC': '5033',
+    'WEST RICHMOND': '5033',
+    'CLARENCE PARK': '5034',
+    'MILLSWOOD': '5034',
+    'WAYVILLE': '5034',
+    'BLACK FOREST': '5035',
+    'EVERARD PARK': '5035',
+    'KESWICK': '5035',
+    'KESWICK TERMINAL': '5035',
+    'GLANDORE': '5037',
+    'KURRALTA PARK': '5037',
+    'NETLEY': '5037',
+    'NORTH PLYMPTON': '5037',
+    'PLYMPTON': '5038',
+    'PLYMPTON PARK': '5038',
+    'SOUTH PLYMPTON': '5038',
+    'CLARENCE GARDENS': '5039',
+    'MELROSE PARK DC': '5039',
+    'NOVAR GARDENS': '5040',
+    'COLONEL LIGHT GARDENS': '5041',
+    'CUMBERLAND PARK': '5041',
+    'DAW PARK': '5041',
+    'PANORAMA': '5041',
+    'WESTBOURNE PARK': '5041',
+    'BEDFORD PARK': '5042',
+    'CLOVELLY PARK': '5042',
+    'FLINDERS UNIVERSITY': '5042',
+    'PASADENA': '5042',
+    'ASCOT PARK': '5043',
+    'MARION': '5043',
+    'MORPHETTVILLE': '5043',
+    'PARK HOLME': '5043',
+    'GLENGOWRIE': '5044',
+    'GLENELG EAST': '5045',
+    'GLENELG JETTY ROAD': '5045',
+    'GLENELG NORTH': '5045',
+    'GLENELG SOUTH': '5045',
+    'OAKLANDS PARK': '5046',
+    'WARRADALE': '5046',
+    'WARRADALE NORTH': '5046',
+    'SEACOMBE GARDENS': '5047',
+    'SEACOMBE HEIGHTS': '5047',
+    'DOVER GARDENS': '5048',
+    'HOVE': '5048',
+    'NORTH BRIGHTON': '5048',
+    'SOUTH BRIGHTON': '5048',
+    'KINGSTON PARK': '5049',
+    'MARINO': '5049',
+    'SEACLIFF': '5049',
+    'SEACLIFF PARK': '5049',
+    'SEAVIEW DOWNS': '5049',
+    'BELLEVUE HEIGHTS': '5050',
+    'EDEN HILLS': '5050',
+    'COROMANDEL VALLEY': '5051',
+    'CRAIGBURN FARM': '5051',
+    'HAWTHORNDENE': '5051',
+    'BELAIR': '5052',
+    'GLENALTA': '5052',
+    'UNLEY': '5061',
+    'UNLEY BC': '5061',
+    'UNLEY DC': '5061',
+    'UNLEY PARK': '5061',
+    'BROWN HILL CREEK': '5062',
+    'CLAPHAM': '5062',
+    'LOWER MITCHAM': '5062',
+    'LYNTON': '5062',
+    'MITCHAM SHOPPING CENTRE': '5062',
+    'TORRENS PARK': '5062',
+    'FREWVILLE': '5063',
+    'FULLARTON': '5063',
+    'HIGHGATE': '5063',
+    'GLEN OSMOND': '5064',
+    'GLENUNGA': '5064',
+    'MOUNT OSMOND': '5064',
+    'MYRTLE BANK': '5064',
+    'ST GEORGES': '5064',
+    'URRBRAE': '5064',
+    'DULWICH': '5065',
+    'GLENSIDE': '5065',
+    'LINDEN PARK': '5065',
+    'TOORAK GARDENS': '5065',
+    'TUSMORE': '5065',
+    'ERINDALE': '5066',
+    'HAZELWOOD PARK': '5066',
+    'STONYFELL': '5066',
+    'WATERFALL GULLY': '5066',
+    'BEULAH PARK': '5067',
+    'KENT TOWN': '5067',
+    'NORWOOD': '5067',
+    'NORWOOD SOUTH': '5067',
+    'ROSE PARK': '5067',
+    'HEATHPOOL': '5068',
+    'KENSINGTON GARDENS': '5068',
+    'KENSINGTON PARK': '5068',
+    'LEABROOK': '5068',
+    'MARRYATVILLE': '5068',
+    'ST MORRIS': '5068',
+    'TRINITY GARDENS': '5068',
+    'COLLEGE PARK': '5069',
+    'EVANDALE': '5069',
+    'HACKNEY': '5069',
+    'MAYLANDS': '5069',
+    'STEPNEY': '5069',
+    'FELIXSTOW': '5070',
+    'FIRLE': '5070',
+    'GLYNDE': '5070',
+    'GLYNDE DC': '5070',
+    'GLYNDE PLAZA': '5070',
+    'JOSLIN': '5070',
+    'MARDEN': '5070',
+    'PAYNEHAM': '5070',
+    'PAYNEHAM SOUTH': '5070',
+    'ROYSTON PARK': '5070',
+    'AULDANA': '5072',
+    'MAGILL': '5072',
+    'MAGILL NORTH': '5072',
+    'MAGILL SOUTH': '5072',
+    'ROSSLYN PARK': '5072',
+    'TERINGIE': '5072',
+    'WOODFORDE': '5072',
+    'HECTORVILLE': '5073',
+    'ROSTREVOR': '5073',
+    'TRANMERE': '5073',
+    'TRANMERE NORTH': '5073',
+    'NEWTON': '5074',
+    'DERNANCOURT': '5075',
+    'ATHELSTONE': '5076',
+    'CASTAMBUL': '5076',
+    'COLLINSWOOD': '5081',
+    'MEDINDIE': '5081',
+    'MEDINDIE GARDENS': '5081',
+    'VALE PARK': '5081',
+    'OVINGHAM': '5082',
+    'PROSPECT EAST': '5082',
+    'PROSPECT WEST': '5082',
+    'THORNGATE': '5082',
+    'BROADVIEW': '5083',
+    'NAILSWORTH': '5083',
+    'SEFTON PARK': '5083',
+    'BLAIR ATHOL WEST': '5084',
+    'KILBURN': '5084',
+    'KILBURN NORTH': '5084',
+    'CLEARVIEW': '5085',
+    'ENFIELD PLAZA': '5085',
+    'NORTHFIELD': '5085',
+    'GILLES PLAINS': '5086',
+    'GREENACRES': '5086',
+    'HAMPSTEAD GARDENS': '5086',
+    'MANNINGHAM': '5086',
+    'OAKDEN': '5086',
+    'KLEMZIG': '5087',
+    'WINDSOR GARDENS': '5087',
+    'HOLDEN HILL': '5088',
+    'HOPE VALLEY': '5090',
+    'BANKSIA PARK': '5091',
+    'TEA TREE GULLY': '5091',
+    'VISTA': '5091',
+    'MODBURY': '5092',
+    'MODBURY HEIGHTS': '5092',
+    'MODBURY NORTH': '5092',
+    'MODBURY NORTH DC': '5092',
+    'PARA VISTA': '5093',
+    'VALLEY VIEW': '5093',
+    'GEPPS CROSS': '5094',
+    'MAWSON LAKES': '5095',
+    'POORAKA': '5095',
+    'THE LEVELS': '5095',
+    'GULFVIEW HEIGHTS': '5096',
+    'PARA HILLS': '5096',
+    'PARA HILLS WEST': '5096',
+    'REDWOOD PARK': '5097',
+    'RIDGEHAVEN': '5097',
+    'INGLE FARM': '5098',
+    'WALKLEY HEIGHTS': '5098',
+    'PARAFIELD': '5106',
+    'PARAFIELD AIRPORT': '5106',
+    'SALISBURY SOUTH': '5106',
+    'SALISBURY SOUTH BC': '5106',
+    'SALISBURY SOUTH DC': '5106',
+    'GREEN FIELDS': '5107',
+    'PARAFIELD GARDENS': '5107',
+    'PARALOWIE': '5108',
+    'SALISBURY DOWNS': '5108',
+    'SALISBURY NORTH': '5108',
+    'SALISBURY NORTH WHITES ROAD': '5108',
+    'BRAHMA LODGE': '5109',
+    'SALISBURY EAST NORTHBRI AVE': '5109',
+    'SALISBURY HEIGHTS': '5109',
+    'SALISBURY PARK': '5109',
+    'SALISBURY PLAIN': '5109',
+    'BOLIVAR': '5110',
+    'DIREK': '5110',
+    'GLOBE DERBY PARK': '5110',
+    'WATERLOO CORNER': '5110',
+    'EDINBURGH': '5111',
+    'ELIZABETH': '5112',
+    'ELIZABETH EAST': '5112',
+    'ELIZABETH GROVE': '5112',
+    'ELIZABETH SOUTH': '5112',
+    'ELIZABETH VALE': '5112',
+    'HILLBANK': '5112',
+    'DAVOREN PARK': '5113',
+    'DAVOREN PARK NORTH': '5113',
+    'DAVOREN PARK SOUTH': '5113',
+    'ELIZABETH DOWNS': '5113',
+    'ELIZABETH NORTH': '5113',
+    'ELIZABETH PARK': '5113',
+    'ELIZABETH WEST': '5113',
+    'ELIZABETH WEST DC': '5113',
+    'ANDREWS FARM': '5114',
+    'BLAKEVIEW': '5114',
+    'CRAIGMORE': '5114',
+    'GOULD CREEK': '5114',
+    'HUMBUG SCRUB': '5114',
+    'ONE TREE HILL': '5114',
+    'SAMPSON FLAT': '5114',
+    'SMITHFIELD PLAINS': '5114',
+    'ULEYBURY': '5114',
+    'KUDLA': '5115',
+    'MUNNO PARA': '5115',
+    'MUNNO PARA DOWNS': '5115',
+    'MUNNO PARA WEST': '5115',
+    'EVANSTON': '5116',
+    'EVANSTON GARDENS': '5116',
+    'EVANSTON PARK': '5116',
+    'EVANSTON SOUTH': '5116',
+    'HILLIER': '5116',
+    'ANGLE VALE': '5117',
+    'BIBARINGA': '5118',
+    'BUCHFELDE': '5118',
+    'CONCORDIA': '5118',
+    'GAWLER': '5118',
+    'GAWLER BELT': '5118',
+    'GAWLER EAST': '5118',
+    'GAWLER RIVER': '5118',
+    'GAWLER SOUTH': '5118',
+    'GAWLER WEST': '5118',
+    'HEWETT': '5118',
+    'KALBEEBA': '5118',
+    'WARD BELT': '5118',
+    'WILLASTON': '5118',
+    'BUCKLAND PARK': '5120',
+    'MACDONALD PARK': '5121',
+    'PENFIELD': '5121',
+    'PENFIELD GARDENS': '5121',
+    'GOLDEN GROVE': '5125',
+    'GOLDEN GROVE VILLAGE': '5125',
+    'GREENWITH': '5125',
+    'FAIRVIEW PARK': '5126',
+    'SURREY DOWNS': '5126',
+    'YATALA VALE': '5126',
+    'WYNN VALE': '5127',
+    'HOUGHTON': '5131',
+    'LOWER HERMITAGE': '5131',
+    'UPPER HERMITAGE': '5131',
+    'PARACOMBE': '5132',
+    'CHERRYVILLE': '5134',
+    'MONTACUTE': '5134',
+    'NORTON SUMMIT': '5136',
+    'ASHTON': '5137',
+    'MARBLE HILL': '5137',
+    'BASKET RANGE': '5138',
+    'FOREST RANGE': '5139',
+    'HORSNELL GULLY': '5141',
+    'SUMMERTOWN': '5141',
+    'URAIDLA': '5142',
+    'CAREY GULLY': '5144',
+    'EAGLE ON THE HILL': '5150',
+    'LEAWOOD GARDENS': '5150',
+    'PICCADILLY': '5151',
+    'CLELAND': '5152',
+    'CRAFERS': '5152',
+    'CRAFERS WEST': '5152',
+    'BIGGS FLAT': '5153',
+    'ECHUNGA': '5153',
+    'FLAXLEY': '5153',
+    'GREEN HILLS RANGE': '5153',
+    'HEATHFIELD': '5153',
+    'IRONBANK': '5153',
+    'JUPITER CREEK': '5153',
+    'MYLOR': '5153',
+    'SCOTT CREEK': '5153',
+    'ALDGATE': '5154',
+    'UPPER STURT': '5156',
+    'BULL CREEK': '5157',
+    'CHERRY GARDENS': '5157',
+    'COROMANDEL EAST': '5157',
+    'DORSET VALE': '5157',
+    'KANGARILLA': '5157',
+    'MCHARG CREEK': '5157',
+    'HALLETT COVE': '5158',
+    "O'HALLORAN HILL": '5158',
+    "O'HALLORAN HILL DC": '5158',
+    'SHEIDOW PARK': '5158',
+    'TROTT PARK': '5158',
+    'ABERFOYLE PARK': '5159',
+    'CHANDLERS HILL': '5159',
+    'FLAGSTAFF HILL': '5159',
+    'LONSDALE': '5160',
+    'LONSDALE DC': '5160',
+    'PORT STANVAC': '5160',
+    'OLD REYNELLA': '5161',
+    'REYNELLA': '5161',
+    'REYNELLA EAST': '5161',
+    'MORPHETT VALE': '5162',
+    'HACKHAM': '5163',
+    'HACKHAM WEST': '5163',
+    'HUNTFIELD HEIGHTS': '5163',
+    'ONKAPARINGA HILLS': '5163',
+    'CHRISTIE DOWNS': '5164',
+    'CHRISTIES BEACH': '5165',
+    'CHRISTIES BEACH NORTH': '5165',
+    "O'SULLIVAN BEACH": '5166',
+    'PORT NOARLUNGA': '5167',
+    'PORT NOARLUNGA SOUTH': '5167',
+    'NOARLUNGA CENTRE': '5168',
+    'NOARLUNGA DOWNS': '5168',
+    'OLD NOARLUNGA': '5168',
+    'MOANA': '5169',
+    'SEAFORD HEIGHTS': '5169',
+    'SEAFORD MEADOWS': '5169',
+    'SEAFORD RISE': '5169',
+    'MASLIN BEACH': '5170',
+    'BLEWITT SPRINGS': '5171',
+    'MCLAREN FLAT': '5171',
+    'MCLAREN VALE': '5171',
+    'PEDLER CREEK': '5171',
+    'TATACHILLA': '5171',
+    'DINGABLEDINGA': '5172',
+    'HOPE FOREST': '5172',
+    'KUITPO': '5172',
+    'KUITPO COLONY': '5172',
+    'KYEEMA': '5172',
+    'MONTARRA': '5172',
+    'PAGES FLAT': '5172',
+    'WHITES VALLEY': '5172',
+    'WILLUNGA': '5172',
+    'WILLUNGA HILL': '5172',
+    'WILLUNGA SOUTH': '5172',
+    'YUNDI': '5172',
+    'ALDINGA': '5173',
+    'ALDINGA BEACH': '5173',
+    'PORT WILLUNGA': '5173',
+    'SILVER SANDS': '5173',
+    'SELLICKS BEACH': '5174',
+    'SELLICKS HILL': '5174',
+    'BLACKFELLOWS CREEK': '5201',
+    'MEADOWS': '5201',
+    'PARIS CREEK': '5201',
+    'PROSPECT HILL': '5201',
+    'HINDMARSH TIERS': '5202',
+    'MYPONGA': '5202',
+    'MYPONGA BEACH': '5202',
+    'PARAWA': '5203',
+    'TORRENS VALE': '5203',
+    'TUNKALILLA': '5203',
+    'YANKALILLA': '5203',
+    'CAPE JERVIS': '5204',
+    'CARRICKALINGA': '5204',
+    'HAY FLAT': '5204',
+    'RAPID BAY': '5204',
+    'SECOND VALLEY': '5204',
+    'WIRRINA COVE': '5204',
+    'MOUNT COMPASS': '5210',
+    'MOUNT MAGNIFICENT': '5210',
+    'NANGKITA': '5210',
+    'BACK VALLEY': '5211',
+    'ENCOUNTER BAY': '5211',
+    'HAYBOROUGH': '5211',
+    'HINDMARSH VALLEY': '5211',
+    'INMAN VALLEY': '5211',
+    'LOWER INMAN VALLEY': '5211',
+    'MCCRACKEN': '5211',
+    'MOUNT JAGGED': '5211',
+    'VICTOR HARBOR': '5211',
+    'WAITPINGA': '5211',
+    'WILLOW CREEK': '5211',
+    'YILKI': '5211',
+    'PORT ELLIOT': '5212',
+    'CURRENCY CREEK': '5214',
+    'GOOLWA': '5214',
+    'GOOLWA BEACH': '5214',
+    'GOOLWA NORTH': '5214',
+    'GOOLWA SOUTH': '5214',
+    'HINDMARSH ISLAND': '5214',
+    'MOSQUITO HILL': '5214',
+    'MUNDOO ISLAND': '5214',
+    'PARNDANA': '5220',
+    'AMERICAN RIVER': '5221',
+    'BALLAST HEAD': '5221',
+    'MUSTON': '5221',
+    'AMERICAN BEACH': '5222',
+    'ANTECHAMBER BAY': '5222',
+    'BAUDIN BEACH': '5222',
+    'BROWNS BEACH': '5222',
+    'CUTTLEFISH BAY': '5222',
+    'DUDLEY EAST': '5222',
+    'DUDLEY WEST': '5222',
+    'IRONSTONE': '5222',
+    'ISLAND BEACH': '5222',
+    'KANGAROO HEAD': '5222',
+    'PELICAN LAGOON': '5222',
+    'PENNESHAW': '5222',
+    'PORKY FLAT': '5222',
+    'SAPPHIRETOWN': '5222',
+    'WILLSON RIVER': '5222',
+    'BAY OF SHOALS': '5223',
+    'BIRCHMORE': '5223',
+    'BROWNLOW KI': '5223',
+    'CAPE BORDA': '5223',
+    'CASSINI': '5223',
+    'CYGNET RIVER': '5223',
+    "D'ESTREES BAY": '5223',
+    'DE MOLE RIVER': '5223',
+    'DUNCAN': '5223',
+    'EMU BAY': '5223',
+    'FLINDERS CHASE': '5223',
+    'GOSSE': '5223',
+    'HAINES': '5223',
+    'HARRIET RIVER': '5223',
+    'KARATTA': '5223',
+    'KINGSCOTE': '5223',
+    'KOHINOOR': '5223',
+    'MACGILLIVRAY': '5223',
+    'MIDDLE RIVER': '5223',
+    'NEPEAN BAY': '5223',
+    'NEWLAND': '5223',
+    'NORTH CAPE': '5223',
+    'SEAL BAY': '5223',
+    'STOKES BAY': '5223',
+    "STUN'SAIL BOOM": '5223',
+    'VIVONNE BAY': '5223',
+    'WESTERN RIVER': '5223',
+    'WISANGER': '5223',
+    'CHAIN OF PONDS': '5231',
+    'KERSBROOK': '5231',
+    'CUDLEE CREEK': '5232',
+    'FORRESTON': '5233',
+    'GUMERACHA': '5233',
+    'EDEN VALLEY': '5235',
+    'FLAXMAN VALLEY': '5235',
+    'SPRINGTON': '5235',
+    'TUNGKILLO': '5236',
+    'APAMURRA': '5237',
+    'MILENDELLA': '5237',
+    'SANDERSTON': '5237',
+    'ANGAS VALLEY': '5238',
+    'BIG BEND': '5238',
+    'BOLTO': '5238',
+    'BOWHILL': '5238',
+    'CAURNAMONT': '5238',
+    'CLAYPANS': '5238',
+    'COWIRRA': '5238',
+    'FIVE MILES': '5238',
+    'FRAHNS': '5238',
+    'FRAYVILLE': '5238',
+    'JULANKA HOLDINGS': '5238',
+    'LAKE CARLET': '5238',
+    'MANNUM': '5238',
+    'NILDOTTIE': '5238',
+    'OLD TEAL FLAT': '5238',
+    'PELLARING FLAT': '5238',
+    'POMPOOTA': '5238',
+    'PONDE': '5238',
+    'PORT MANNUM': '5238',
+    'PUNTHARI': '5238',
+    'PURNONG': '5238',
+    'PURNONG LANDING': '5238',
+    'TEAL FLAT': '5238',
+    'WALKER FLAT': '5238',
+    'WALL FLAT': '5238',
+    'WONGULLA': '5238',
+    'WOODLANE': '5238',
+    'YOUNGHUSBAND': '5238',
+    'YOUNGHUSBAND HOLDINGS': '5238',
+    'LENSWOOD': '5240',
+    'LOBETHAL': '5241',
+    'BALHANNAH': '5242',
+    'OAKBANK': '5243',
+    'CHARLESTON': '5244',
+    'HARROGATE': '5244',
+    'INVERBRACKIE': '5244',
+    'MOUNT TORRENS': '5244',
+    'HAHNDORF': '5245',
+    'PAECHTOWN': '5245',
+    'VERDUN': '5245',
+    'BLAKISTON': '5250',
+    'LITTLEHAMPTON': '5250',
+    'TOTNESS': '5250',
+    'BUGLE RANGES': '5251',
+    'MOUNT BARKER': '5251',
+    'MOUNT BARKER JUNCTION': '5251',
+    'MOUNT BARKER SPRINGS': '5251',
+    'MOUNT BARKER SUMMIT': '5251',
+    'WISTOW': '5251',
+    'BRUKUNGA': '5252',
+    'DAWESLEY': '5252',
+    'HAY VALLEY': '5252',
+    'KANMANTOO': '5252',
+    'NAIRNE': '5252',
+    'AVOCA DELL': '5253',
+    'BRINKLEY': '5253',
+    'BURDETT': '5253',
+    'CHAPMAN BORE': '5253',
+    'GIFFORD HILL': '5253',
+    'GREENBANKS': '5253',
+    'MOBILONG': '5253',
+    'MURRAWONG': '5253',
+    'MURRAY BRIDGE': '5253',
+    'MURRAY BRIDGE EAST': '5253',
+    'MURRAY BRIDGE NORTH': '5253',
+    'MURRAY BRIDGE SOUTH': '5253',
+    'NORTHERN HEIGHTS': '5253',
+    'RIVERGLADES': '5253',
+    'RIVERGLEN': '5253',
+    'ROCKY GULLY': '5253',
+    'SWANPORT': '5253',
+    'WHITE HILL': '5253',
+    'WHITE SANDS': '5253',
+    'WILLOW BANKS': '5253',
+    'CALLINGTON': '5254',
+    'CALOOTE': '5254',
+    'MONARTO': '5254',
+    'MONARTO SOUTH': '5254',
+    'MONTEITH': '5254',
+    'MYPOLONGA': '5254',
+    'PALLAMANA': '5254',
+    'PETWOOD': '5254',
+    'ROCKLEIGH': '5254',
+    'TEPKO': '5254',
+    'ZADOWS LANDING': '5254',
+    'ANGAS PLAINS': '5255',
+    'BELVIDERE': '5255',
+    'BLETCHLEY': '5255',
+    'FINNISS': '5255',
+    'GEMMELLS': '5255',
+    'HIGHLAND VALLEY': '5255',
+    'LAKE PLAINS': '5255',
+    'LANGHORNE CREEK': '5255',
+    'MOUNT OBSERVATION': '5255',
+    'MULGUNDAWA': '5255',
+    'NALPA': '5255',
+    'RED CREEK': '5255',
+    'SALEM': '5255',
+    'SANDERGROVE': '5255',
+    'STRATHALBYN': '5255',
+    'TOOPERANG': '5255',
+    'WILLYAROO': '5255',
+    'WOODCHESTER': '5255',
+    'CLAYTON BAY': '5256',
+    'MILANG': '5256',
+    'NURRAGI': '5256',
+    'POINT STURT': '5256',
+    'TOLDEROL': '5256',
+    'ASHVILLE': '5259',
+    'JERVOIS': '5259',
+    'KEPA': '5259',
+    'MALINONG': '5259',
+    'NATURI': '5259',
+    'POINT MCLEAY': '5259',
+    'POLTALLOCH': '5259',
+    'RAUKKAN': '5259',
+    'TAILEM BEND': '5259',
+    'WELLINGTON EAST': '5259',
+    'ELWOMPLE': '5260',
+    'COOKE PLAINS': '5261',
+    'COOMANDOOK': '5261',
+    'CULBURRA': '5261',
+    'KI KI': '5261',
+    'YUMALI': '5261',
+    'BINNUM': '5262',
+    'FRANCES': '5262',
+    'HYNAM': '5262',
+    'KYBYBOLITE': '5262',
+    'COORONG': '5264',
+    'MENINGIE': '5264',
+    'MENINGIE EAST': '5264',
+    'MENINGIE WEST': '5264',
+    'POLICEMAN POINT': '5264',
+    'SALT CREEK': '5264',
+    'WALTOWA': '5264',
+    'COONALPYN': '5265',
+    'FIELD': '5265',
+    'BUNBURY': '5266',
+    'COLEBATCH': '5266',
+    'TINTINARA': '5266',
+    'BRIMBAGO': '5267',
+    'COOMBE': '5267',
+    'KEITH': '5267',
+    'LAFFER': '5267',
+    'MAKIN': '5267',
+    'MCCALLUM': '5267',
+    'MOUNT CHARLES': '5267',
+    'PETHERICK': '5267',
+    'SHAUGH': '5267',
+    'WILLALOOKA': '5267',
+    'WIRREGA': '5267',
+    'BANGHAM': '5268',
+    'BORDERTOWN': '5268',
+    'BORDERTOWN SOUTH': '5268',
+    'CANNAWIGARA': '5268',
+    'LOWAN VALE': '5268',
+    'POOGINAGORIC': '5268',
+    'SENIOR': '5268',
+    'WESTERN FLAT': '5268',
+    'WOLSELEY': '5269',
+    'CAREW': '5270',
+    'CUSTON': '5270',
+    'KONGAL': '5270',
+    'MUNDULLA': '5270',
+    'MUNDULLA WEST': '5270',
+    'SWEDE FLAT': '5270',
+    'BOOL LAGOON': '5271',
+    'JOANNA': '5271',
+    'KEPPOCH': '5271',
+    'KOPPAMURRA': '5271',
+    'LAURIE PARK': '5271',
+    'LOCHABER': '5271',
+    'MARCOLLAT': '5271',
+    'MOUNT LIGHT': '5271',
+    'MOYHALL': '5271',
+    'NARACOORTE': '5271',
+    'PADTHAWAY': '5271',
+    'STEWART RANGE': '5271',
+    'STRUAN': '5271',
+    'WRATTONBULLY': '5271',
+    'COLES': '5272',
+    'CONMURRA': '5272',
+    'FOX': '5272',
+    'GREENWAYS': '5272',
+    'LUCINDALE': '5272',
+    'WOOLUMBOOL': '5272',
+    'AVENUE RANGE': '5273',
+    'BLACKFORD': '5275',
+    'BOATSWAIN POINT': '5275',
+    'CAPE JAFFA': '5275',
+    'KEILIRA': '5275',
+    'KINGSTON SE': '5275',
+    'MOUNT BENSON': '5275',
+    'PINKS BEACH': '5275',
+    'ROSETOWN': '5275',
+    'SANDY GROVE': '5275',
+    'TARATAP': '5275',
+    'TILLEY SWAMP': '5275',
+    'WANGOLINA': '5275',
+    'WEST RANGE': '5275',
+    'WYOMI': '5275',
+    'BRAY': '5276',
+    'NORA CREINA': '5276',
+    'ROBE': '5276',
+    'COMAUM': '5277',
+    'MAAOUPE': '5277',
+    'MONBULLA': '5277',
+    'NANGWARRY': '5277',
+    'PENOLA': '5277',
+    'PLEASANT PARK': '5277',
+    'TARPEENA': '5277',
+    'KALANGADOO': '5278',
+    'KRONGART': '5278',
+    'MOERLONG': '5278',
+    'WEPAR': '5278',
+    'KOORINE': '5279',
+    'MOUNT BURR': '5279',
+    'MOUNT MCINTYRE': '5279',
+    'SHORT': '5279',
+    'TRIHI': '5279',
+    'WATTLE RANGE EAST': '5279',
+    'BEACHPORT': '5280',
+    'CLAY WELLS': '5280',
+    'FURNER': '5280',
+    'GERMAN CREEK': '5280',
+    'GERMAN FLAT': '5280',
+    'HATHERLEIGH': '5280',
+    'KANGAROO INN': '5280',
+    'MAGAREY': '5280',
+    'MILLICENT': '5280',
+    'RENDELSHAM': '5280',
+    'ROCKY CAMP': '5280',
+    'SOUTHEND': '5280',
+    'TANTANOOLA': '5280',
+    'THORNLEA': '5280',
+    'WATTLE RANGE': '5280',
+    'MOUNT GAMBIER': '5290',
+    'MOUNT GAMBIER DC': '5290',
+    'ALLENDALE EAST': '5291',
+    'BLACKFELLOWS CAVES': '5291',
+    'BURRUNGULE': '5291',
+    'CANUNDA': '5291',
+    'CAPE DOUGLAS': '5291',
+    'CAROLINE': '5291',
+    'CARPENTER ROCKS': '5291',
+    'CAVETON': '5291',
+    'COMPTON': '5291',
+    'DISMAL SWAMP': '5291',
+    'DONOVANS': '5291',
+    'GLENBURNIE': '5291',
+    'GLENCOE WEST': '5291',
+    'KONGORONG': '5291',
+    'MILLEL': '5291',
+    'MINGBOOL': '5291',
+    'MOORAK': '5291',
+    'MOUNT GAMBIER EAST': '5291',
+    'MOUNT GAMBIER WEST': '5291',
+    'MOUNT SCHANK': '5291',
+    'NENE VALLEY': '5291',
+    'O B FLAT': '5291',
+    'PELICAN POINT': '5291',
+    'PORT MACDONNELL': '5291',
+    'RACECOURSE BAY': '5291',
+    'SQUARE MILE': '5291',
+    'SUTTONTOWN': '5291',
+    'WANDILO': '5291',
+    'WORROLONG': '5291',
+    'WYE': '5291',
+    'YAHL': '5291',
+    'CARCUMA': '5301',
+    'GERANIUM': '5301',
+    'JABUK': '5301',
+    'MOORLANDS': '5301',
+    'NETHERTON': '5301',
+    'PARRAKIE': '5301',
+    'PEAKE': '5301',
+    'SHERLOCK': '5301',
+    'WILKAWATT': '5301',
+    'LAMEROO': '5302',
+    'NGARKAT': '5302',
+    'PARILLA': '5303',
+    'KRINGIN': '5304',
+    'PEEBINGA': '5304',
+    'PINNAROO': '5304',
+    'WYNARKA': '5306',
+    'KAROONDA': '5307',
+    'KARTE': '5307',
+    'KULKAMI': '5307',
+    'MARAMA': '5307',
+    'MOOTATUNGA': '5307',
+    'COPEVILLE': '5308',
+    'GALGA': '5308',
+    'KALYAN': '5308',
+    'MAGGEA': '5308',
+    'MANTUNG': '5308',
+    'MERCUNDA': '5308',
+    'PERPONDA': '5308',
+    'BORRIKA': '5309',
+    'HALIDON': '5309',
+    'MINDARIE': '5309',
+    'SANDALWOOD': '5309',
+    'CALIPH': '5310',
+    'WANBI': '5310',
+    'ALAWOONA': '5311',
+    'BILLIATT': '5311',
+    'BUGLE HUT': '5311',
+    'MALPAS': '5311',
+    'MERIBAH': '5311',
+    'PARUNA': '5311',
+    'SCHELL WELL': '5311',
+    'TALDRA': '5311',
+    'WUNKAR': '5311',
+    'VEITCH': '5312',
+    'BEATTY': '5320',
+    'BEAUMONTS': '5320',
+    'BRENDA PARK': '5320',
+    'BUNDEY': '5320',
+    'EBA': '5320',
+    'LINDLEY': '5320',
+    'MORGAN': '5320',
+    'MORPHETTS FLAT': '5320',
+    'MURBKO': '5320',
+    'NORTH WEST BEND': '5320',
+    'WOMBATS REST': '5320',
+    'CADELL': '5321',
+    'CADELL LAGOON': '5321',
+    'GOLDEN HEIGHTS': '5322',
+    'QUALCO': '5322',
+    'RAMCO': '5322',
+    'RAMCO HEIGHTS': '5322',
+    'SUNLANDS': '5322',
+    'BOOLGUN': '5330',
+    'DEVLINS POUND': '5330',
+    'GOOD HOPE LANDING': '5330',
+    'HOLDER SIDING': '5330',
+    'KANNI': '5330',
+    'LOWBANK': '5330',
+    'MARKARANKA': '5330',
+    'OVERLAND CORNER': '5330',
+    'POOGINOOK': '5330',
+    'STOCKYARD PLAIN': '5330',
+    'TAYLORVILLE': '5330',
+    'WAIKERIE': '5330',
+    'WOOLPUNDA': '5330',
+    'KINGSTON ON MURRAY': '5331',
+    'MOOROOK': '5332',
+    'MOOROOK SOUTH': '5332',
+    'WAPPILKA': '5332',
+    'YINKANIE': '5332',
+    'BOOKPURNONG': '5333',
+    'LOXTON': '5333',
+    'LOXTON NORTH': '5333',
+    'NEW RESIDENCE': '5333',
+    'PATA': '5333',
+    'PYAP': '5333',
+    'PYAP WEST': '5333',
+    'TAPLAN': '5333',
+    'MUNDIC CREEK': '5340',
+    'MURTHO': '5340',
+    'PARINGA': '5340',
+    'PIKE RIVER': '5340',
+    'WONUARRA': '5340',
+    'CHAFFEY': '5341',
+    'COOLTONG': '5341',
+    'CRESCENT': '5341',
+    'OLD CALPERUM': '5341',
+    'RENMARK': '5341',
+    'RENMARK NORTH': '5341',
+    'RENMARK SOUTH': '5341',
+    'RENMARK WEST': '5341',
+    'BERRI': '5343',
+    'GERARD': '5343',
+    'GURRA GURRA': '5343',
+    'KATARAPKO': '5343',
+    'LYRUP': '5343',
+    'WINKIE': '5343',
+    'GLOSSOP': '5344',
+    'BARMERA': '5345',
+    'LOVEDAY': '5345',
+    'SPECTACLE LAKE': '5345',
+    'COBDOGLA': '5346',
+    'BAROSSA GOLDFIELDS': '5351',
+    'COCKATOO VALLEY': '5351',
+    'LYNDOCH': '5351',
+    'MOUNT CRAWFORD': '5351',
+    'PEWSEY VALE': '5351',
+    'BETHANY': '5352',
+    'GOMERSAL': '5352',
+    'KRONDORF': '5352',
+    'ROWLAND FLAT': '5352',
+    'STONE WELL': '5352',
+    'TANUNDA': '5352',
+    'VINE VALE': '5352',
+    'ANGASTON': '5353',
+    'CAMBRAI': '5353',
+    'KEYNETON': '5353',
+    'KONGOLIA': '5353',
+    'MOCULTA': '5353',
+    'MOUNT MCKENZIE': '5353',
+    'PENRICE': '5353',
+    'SEDAN': '5353',
+    'TOWITTA': '5353',
+    'BAKARA': '5354',
+    'BAKARA WELL': '5354',
+    'GREENWAYS LANDING': '5354',
+    'LANGS LANDING': '5354',
+    'MARKS LANDING': '5354',
+    'NAIDIA': '5354',
+    'PUNYELROO': '5354',
+    'SUNNYDALE': '5354',
+    'DAVEYSTON': '5355',
+    'LIGHT PASS': '5355',
+    'MARANANGA': '5355',
+    'MOPPA': '5355',
+    'NURIOOTPA': '5355',
+    'SEPPELTSFIELD': '5355',
+    'STOCKWELL': '5355',
+    'ANNADALE': '5356',
+    'DUTTON': '5356',
+    'DUTTON EAST': '5356',
+    'SANDLETON': '5356',
+    'ST KITTS': '5356',
+    'STEINFELD': '5356',
+    'TRURO': '5356',
+    'BLANCHETOWN': '5357',
+    'MCBEAN POUND': '5357',
+    'NEW WELL': '5357',
+    'NOTTS WELL': '5357',
+    'PAISLEY': '5357',
+    'WIGLEY FLAT': '5357',
+    'GREENOCK': '5360',
+    'NAIN': '5360',
+    'MORN HILL': '5371',
+    'ROSEWORTHY': '5371',
+    'SHEAOAK LOG': '5371',
+    'TEMPLERS': '5371',
+    'FREELING': '5372',
+    'ALLENDALE NORTH': '5373',
+    'BAGOT WELL': '5373',
+    'BETHEL': '5373',
+    'FORDS': '5373',
+    'KAPUNDA': '5373',
+    'KOONUNGA': '5373',
+    'ST JOHNS': '5373',
+    'AUSTRALIA PLAINS': '5374',
+    'BOWER': '5374',
+    'BROWNLOW': '5374',
+    'EUDUNDA': '5374',
+    'FRANKTON': '5374',
+    'HANSBOROUGH': '5374',
+    'JULIA': '5374',
+    'MOUNT MARY': '5374',
+    'NEALES FLAT': '5374',
+    'NGAPALA': '5374',
+    'PEEP HILL': '5374',
+    'POINT PASS': '5374',
+    'SUTHERLANDS': '5374',
+    'BRADY CREEK': '5381',
+    'EMU DOWNS': '5381',
+    'GERANIUM PLAINS': '5381',
+    'HALLELUJAH HILLS': '5381',
+    'ROBERTSTOWN': '5381',
+    'WORLDS END CREEK': '5381',
+    'MAGDALLA': '5400',
+    'PINKERTON PLAINS': '5400',
+    'WASLEYS': '5400',
+    'WOOLSHEDS': '5400',
+    'HAMLEY BRIDGE': '5401',
+    'SALTER SPRINGS': '5401',
+    'LINWOOD': '5410',
+    'STOCKPORT': '5410',
+    'GILES CORNER': '5411',
+    'TARLEE': '5411',
+    'NAVAN': '5412',
+    'RHYNIE': '5412',
+    'APOINGA': '5413',
+    'MARRABEL': '5413',
+    'SADDLEWORTH': '5413',
+    'STEELTON': '5413',
+    'TARNMA': '5413',
+    'TOTHILL BELT': '5413',
+    'TOTHILL CREEK': '5413',
+    'MINTARO': '5415',
+    'FARRELL FLAT': '5416',
+    'PORTER LAGOON': '5416',
+    'BALDINA': '5417',
+    'BOOBOROWIE': '5417',
+    'BURRA EASTERN DISTRICTS': '5417',
+    'GUM CREEK': '5417',
+    'HANSON': '5417',
+    'KOONOONA': '5417',
+    'LEIGHTON': '5417',
+    'MONGOLATA': '5417',
+    'NORTH BOOBOROWIE': '5417',
+    'MOUNT BRYAN': '5418',
+    'CANOWIE': '5419',
+    'HALLETT': '5419',
+    'MOUNT BRYAN EAST': '5419',
+    'ULOOLOO': '5419',
+    'WILLALO': '5419',
+    'WONNA': '5419',
+    'CANOWIE BELT': '5420',
+    'WHYTE YARCOWIE': '5420',
+    'FRANKLYN': '5421',
+    'TEROWIE': '5421',
+    'CAVENAGH': '5422',
+    'HARDY': '5422',
+    'MANNANARIE': '5422',
+    'MINVALARA': '5422',
+    'OODLA WIRRA': '5422',
+    'PARATOO': '5422',
+    'PARNAROO': '5422',
+    'SUNNYBRAE': '5422',
+    'UCOLTA': '5422',
+    'YATINA': '5422',
+    'AMYTON': '5431',
+    'COOMOOROO': '5431',
+    'EURELIA': '5431',
+    'HAMMOND': '5431',
+    'JOHNBURGH': '5431',
+    'MINBURRA': '5431',
+    'MORCHARD': '5431',
+    'ORROROO': '5431',
+    'PEKINA': '5431',
+    'TARCOWIE': '5431',
+    'WALLOWAY': '5431',
+    'WILLOWIE': '5431',
+    'YALPARA': '5431',
+    'BELTON': '5432',
+    'CARRIETON': '5432',
+    'CRADOCK': '5432',
+    'MOOCKRA': '5432',
+    'YANYARRIE': '5432',
+    'QUORN': '5433',
+    'SALTIA': '5433',
+    'STEPHENSTON': '5433',
+    'WILLOCHRA': '5433',
+    'YARRAH': '5433',
+    'BARNDIOOTA': '5434',
+    'KANYAKA': '5434',
+    'COCKBURN': '5440',
+    'MANNA HILL': '5440',
+    'MINGARY': '5440',
+    'NACKARA': '5440',
+    'OLARY': '5440',
+    'WAUKARINGA': '5440',
+    'YUNTA': '5440',
+    'UNDALYA': '5451',
+    'LEASINGHAM': '5452',
+    'WATERVALE': '5452',
+    'ARMAGH': '5453',
+    'BARINIA': '5453',
+    'BENBOURNIE': '5453',
+    'BOCONNOC PARK': '5453',
+    'GILLENTOWN': '5453',
+    'HILL RIVER': '5453',
+    'HOYLETON': '5453',
+    'KYBUNGA': '5453',
+    'PENWORTHAM': '5453',
+    'POLISH HILL RIVER': '5453',
+    'SEVENHILL': '5453',
+    'STANLEY FLAT': '5453',
+    'ANDREWS': '5454',
+    'BROUGHTON RIVER VALLEY': '5454',
+    'EUROMINA': '5454',
+    'HACKLINS CORNER': '5454',
+    'SPALDING': '5454',
+    'HILLTOWN': '5455',
+    'BARABBA': '5460',
+    'OWEN': '5460',
+    'PINERY': '5460',
+    'BALAKLAVA': '5461',
+    'BOWILLIA': '5461',
+    'DALKEY': '5461',
+    'ERITH': '5461',
+    'EVERARD CENTRAL': '5461',
+    'GOYDER': '5461',
+    'HALBURY': '5461',
+    'HOSKIN CORNER': '5461',
+    'MOUNT TEMPLETON': '5461',
+    'SAINTS': '5461',
+    'STOW': '5461',
+    'WATCHMAN': '5461',
+    'WHITWARTA': '5461',
+    'BLYTH': '5462',
+    'ANAMA': '5464',
+    'BRINKWORTH': '5464',
+    'CONDOWIE': '5464',
+    'KOOLUNGA': '5464',
+    'MAROLA': '5464',
+    'YACKA': '5470',
+    'GULNARE': '5471',
+    'APPILA': '5480',
+    'STONE HUT': '5480',
+    'MURRAY TOWN': '5481',
+    'WIRRABARA': '5481',
+    'WONGYARRA': '5481',
+    'BOOLEROO CENTRE': '5482',
+    'WEPOWIE': '5482',
+    'WILMINGTON': '5485',
+    'CALTOWIE': '5490',
+    'CALTOWIE NORTH': '5490',
+    'CALTOWIE WEST': '5490',
+    'BELALIE EAST': '5491',
+    'BELALIE NORTH': '5491',
+    'BUNDALEER GARDENS': '5491',
+    'BUNDALEER NORTH': '5491',
+    'HORNSDALE': '5491',
+    'JAMESTOWN': '5491',
+    'WEST BUNDALEER': '5491',
+    'YONGALA': '5493',
+    'BAROOTA': '5495',
+    'GERMEIN BAY': '5495',
+    'MAMBRAY CREEK': '5495',
+    'NECTAR BROOK': '5495',
+    'PORT FLINDERS': '5495',
+    'PORT GERMEIN': '5495',
+    'CALOMBA': '5501',
+    'DUBLIN': '5501',
+    'LEWISTON': '5501',
+    'LONG PLAINS': '5501',
+    'LOWER LIGHT': '5501',
+    'MIDDLE BEACH': '5501',
+    'PARHAM': '5501',
+    'PORT GAWLER': '5501',
+    'THOMPSON BEACH': '5501',
+    'TWO WELLS': '5501',
+    'WEBB BEACH': '5501',
+    'WILD HORSE PLAINS': '5501',
+    'FISCHER': '5502',
+    'GRACE PLAINS': '5502',
+    'KORUNYE': '5502',
+    'MALLALA': '5502',
+    'RED BANKS': '5502',
+    'REEVES PLAINS': '5502',
+    'BARUNGA GAP': '5520',
+    'BUMBUNGA': '5520',
+    'BURNSFIELD': '5520',
+    'GLEESON HILL': '5520',
+    'SNOWTOWN': '5520',
+    'WOKURNA': '5520',
+    'REDHILL': '5521',
+    'FISHERMAN BAY': '5522',
+    'LOWER BROUGHTON': '5522',
+    'PORT BROUGHTON': '5522',
+    'WARD HILL': '5522',
+    'BEETALOO': '5523',
+    'CLEMENTS GAP': '5523',
+    'HUDDLESTON': '5523',
+    'MERRITON': '5523',
+    'NARRIDY': '5523',
+    'NUROM': '5523',
+    'WANDEARAH': '5523',
+    'WANDEARAH EAST': '5523',
+    'WANDEARAH WEST': '5523',
+    'BUNGAMA': '5540',
+    'COONAMIA': '5540',
+    'NAPPERBY': '5540',
+    'NELSHABY': '5540',
+    'PIRIE EAST': '5540',
+    'PORT DAVIS': '5540',
+    'PORT PIRIE': '5540',
+    'PORT PIRIE SOUTH': '5540',
+    'PORT PIRIE WEST': '5540',
+    'RISDON PARK': '5540',
+    'RISDON PARK SOUTH': '5540',
+    'SOLOMONTOWN': '5540',
+    'TELOWIE': '5540',
+    'WARNERTOWN': '5540',
+    'BOWMANS': '5550',
+    'KALLORA': '5550',
+    'NANTAWARRA': '5550',
+    'PORT WAKEFIELD': '5550',
+    'PROOF RANGE': '5550',
+    'SOUTH HUMMOCKS': '5550',
+    'KAINTON': '5552',
+    'PASKEVILLE': '5552',
+    'SUNNYVALE': '5552',
+    'THRINGTON': '5552',
+    'BOORS PLAIN': '5554',
+    'CUNLIFFE': '5554',
+    'JERUSALEM': '5554',
+    'MATTA FLAT': '5554',
+    'NEW TOWN': '5554',
+    'THOMAS PLAIN': '5554',
+    'WALLAROO MINES': '5554',
+    'WILLAMULKA': '5554',
+    'ALFORD': '5555',
+    'COLLINSFIELD': '5555',
+    'DOWLING': '5555',
+    'DOWLINGVILLE': '5555',
+    'HOPE GAP': '5555',
+    'KULPARA': '5555',
+    'LAKE VIEW': '5555',
+    'MUNDOORA': '5555',
+    'TICKERA': '5555',
+    'WINULTA': '5555',
+    'NORTH BEACH': '5556',
+    'WALLAROO PLAIN': '5556',
+    'WARBURTO': '5556',
+    'AGERY': '5558',
+    'EAST MOONTA': '5558',
+    'HAMLEY': '5558',
+    'KOOROONA': '5558',
+    'MOONTA': '5558',
+    'MOONTA BAY': '5558',
+    'MOONTA MINES': '5558',
+    'NALYAPPA': '5558',
+    'NORTH MOONTA': '5558',
+    'NORTH YELTA': '5558',
+    'PARAMATTA': '5558',
+    'PORT HUGHES': '5558',
+    'BUTE': '5560',
+    'NINNES': '5560',
+    'CLINTON CENTRE': '5570',
+    'PORT CLINTON': '5570',
+    'PRICE': '5570',
+    'ARDROSSAN': '5571',
+    'BLACK POINT': '5571',
+    'JAMES WELL': '5571',
+    'PETERSVILLE': '5571',
+    'PINE POINT': '5571',
+    'ROGUES POINT': '5571',
+    'TIDDY WIDDY BEACH': '5571',
+    'ARTHURTON': '5572',
+    'PORT ARTHUR': '5572',
+    'CHINAMAN WELLS': '5573',
+    'POINT PEARCE': '5573',
+    'PORT VICTORIA': '5573',
+    'SOUTH KILKERRAN': '5573',
+    'URANIA': '5573',
+    'WAURALTEE': '5573',
+    'WEETULTA': '5573',
+    'YORKE VALLEY': '5573',
+    'BLUFF BEACH': '5575',
+    'BRENTWOOD': '5575',
+    'CORNY POINT': '5575',
+    'COUCH BEACH': '5575',
+    'HARDWICKE BAY': '5575',
+    'KOOLYWURTIE': '5575',
+    'MARION BAY': '5575',
+    'MINLATON': '5575',
+    'POINT SOUTTAR': '5575',
+    'POINT TURTON': '5575',
+    'PORT JULIA': '5575',
+    'PORT RICKABY': '5575',
+    'STENHOUSE BAY': '5575',
+    'WHITE HUT': '5575',
+    'WOOL BAY': '5575',
+    'HONITON': '5576',
+    'PORT MOOROWIE': '5576',
+    'YORKETOWN': '5576',
+    'FOUL BAY': '5577',
+    'INNESTON': '5577',
+    'WAROOKA': '5577',
+    'CURRAMULKA': '5580',
+    'PORT VINCENT': '5581',
+    'SHEAOAK FLAT': '5581',
+    'PORT GILES': '5582',
+    'STANSBURY': '5582',
+    'COOBOWIE': '5583',
+    'EDITHBURGH': '5583',
+    'CULTANA': '5600',
+    'IRON BARON': '5600',
+    'MULLAQUANA': '5600',
+    'POINT LOWLY': '5600',
+    'PORT BONYTHON': '5600',
+    'WHYALLA': '5600',
+    'WHYALLA DC': '5600',
+    'WHYALLA PLAYFORD': '5600',
+    'IRON KNOB': '5601',
+    'COWELL': '5602',
+    'LUCKY BAY': '5602',
+    'MANGALO': '5602',
+    'MILTALIE': '5602',
+    'MINBRIE': '5602',
+    'MITCHELLVILLE': '5602',
+    'PORT GIBBON': '5602',
+    'ARNO BAY': '5603',
+    'HINCKS': '5603',
+    'VERRAN': '5603',
+    'WHARMINDA': '5603',
+    'PORT NEILL': '5604',
+    'BUTLER': '5605',
+    'TUMBY BAY': '5605',
+    'KIRTON POINT': '5606',
+    'PORT LINCOLN': '5606',
+    'BOSTON': '5607',
+    'BROOKER': '5607',
+    'CHARLTON GULLY': '5607',
+    'COFFIN BAY': '5607',
+    'COOMUNGA': '5607',
+    'COULTA': '5607',
+    'DUCK PONDS': '5607',
+    'FOUNTAIN': '5607',
+    'GREEN PATCH': '5607',
+    'HAWSON': '5607',
+    'HORSE PENINSULA': '5607',
+    'KARKOO': '5607',
+    'KELLIDIE BAY': '5607',
+    'KIANA': '5607',
+    'KOPPIO': '5607',
+    'LINCOLN NATIONAL PARK': '5607',
+    'LIPSON': '5607',
+    'LOUTH BAY': '5607',
+    'MOODY': '5607',
+    'MOUNT DRUMMOND': '5607',
+    'MOUNT DUTTON BAY': '5607',
+    'MURDINGA': '5607',
+    'NORTH SHIELDS': '5607',
+    'PEACHNA': '5607',
+    'PEARLAH': '5607',
+    'POONINDIE': '5607',
+    'SHERINGA': '5607',
+    'SLEAFORD': '5607',
+    'SULLIVAN': '5607',
+    'TOOLIGIE': '5607',
+    'TOOTENILLA': '5607',
+    'TULKA': '5607',
+    'TULKA NORTH': '5607',
+    'ULEY': '5607',
+    'UNGARRA': '5607',
+    'WANGARY': '5607',
+    'WANILLA': '5607',
+    'WARRACHIE': '5607',
+    'WARROW': '5607',
+    'WARUNDA': '5607',
+    'WHITES FLAT': '5607',
+    'WHITES RIVER': '5607',
+    'YALLUNDA FLAT': '5607',
+    'WHYALLA NORRIE': '5608',
+    'WHYALLA NORRIE EAST': '5608',
+    'WHYALLA NORRIE NORTH': '5608',
+    'WHYALLA STUART': '5608',
+    'WHYALLA JENKINS': '5609',
+    'EDILLILIE': '5630',
+    'COCKALEECHIE': '5631',
+    'CUMMINS': '5631',
+    'KAPINNIE': '5632',
+    'YEELANNA': '5632',
+    'BOONERDO': '5633',
+    'LOCK': '5633',
+    'CAMPOONA': '5640',
+    'CLEVE': '5640',
+    'WADDIKEE': '5640',
+    'BARNA': '5641',
+    'BUCKLEBOO': '5641',
+    'CARALUE': '5641',
+    'CORTLINYE': '5641',
+    'CUNYARIE': '5641',
+    'KELLY': '5641',
+    'KIMBA': '5641',
+    'MOSELEY': '5641',
+    'PANITYA': '5641',
+    'PINKAWILLINIE': '5641',
+    'SOLOMON': '5641',
+    'WILCHERRY': '5641',
+    'YALANDA': '5641',
+    'DARKE PEAK': '5642',
+    'HAMBIDGE': '5642',
+    'KIELPA': '5642',
+    'MURLONG': '5642',
+    'RUDALL': '5642',
+    'COOTRA': '5650',
+    'KOONGAWA': '5650',
+    'WARRAMBOO': '5650',
+    'KYANCUTTA': '5651',
+    'PANEY': '5652',
+    'WUDINNA': '5652',
+    'YANINEE': '5653',
+    'COCATA': '5654',
+    'KARCULTABY': '5654',
+    'MINNIPA': '5654',
+    'MOUNT DAMPER': '5654',
+    'BOCKELBERG': '5655',
+    'KALDOONERA': '5655',
+    'POOCHERA': '5655',
+    'PYGERY': '5655',
+    'CHILPENUNDA': '5660',
+    'CUNGENA': '5660',
+    'KOOLGERA': '5661',
+    'PIMBAACLA': '5661',
+    'WALLALA': '5661',
+    'WIRRULLA': '5661',
+    'YANTANABIE': '5661',
+    'BRAMFIELD': '5670',
+    'COLTON': '5670',
+    'COOLILLIE': '5670',
+    'ELLISTON': '5670',
+    'KAPPAWANTA': '5670',
+    'MOUNT JOY': '5670',
+    'MOUNT WEDGE': '5670',
+    'PALKAGEE': '5670',
+    'POLDA': '5670',
+    'TALIA': '5670',
+    'ULYERRA': '5670',
+    'BAIRD BAY': '5671',
+    'CALCA': '5671',
+    'COLLEY': '5671',
+    'MORTANA': '5671',
+    'PORT KENNY': '5671',
+    'TYRINGA': '5671',
+    'WITERA': '5671',
+    'CARAWA': '5680',
+    'CHANDADA': '5680',
+    'CHINBINGINA': '5680',
+    'EBA ANCHORAGE': '5680',
+    'HASLAM': '5680',
+    'INKSTER': '5680',
+    'LAURA BAY': '5680',
+    'MUDAMUCKLA': '5680',
+    'NUNJIKOMPITA': '5680',
+    'PERLUBIE': '5680',
+    'PETINA': '5680',
+    'PIEDNIPPIE': '5680',
+    'PUNTABIE': '5680',
+    'PUREBA': '5680',
+    'SCEALE BAY': '5680',
+    'SMOKY BAY': '5680',
+    'STREAKY BAY': '5680',
+    'WESTALL': '5680',
+    'YANERBIE': '5680',
+    'BOOKABIE': '5690',
+    'CEDUNA': '5690',
+    'CHARRA': '5690',
+    'COORABIE': '5690',
+    'DENIAL BAY': '5690',
+    'FOWLERS BAY': '5690',
+    'KALANBI': '5690',
+    'KOONIBBA': '5690',
+    'MALTEE': '5690',
+    'MERGHINY': '5690',
+    'NADIA': '5690',
+    'NUNDROO': '5690',
+    'PENONG': '5690',
+    'THEVENARD': '5690',
+    'UWORRA': '5690',
+    'WANDANA': '5690',
+    'WATRABA': '5690',
+    'WHITE WELL CORNER': '5690',
+    'YALATA': '5690',
+    'BLANCHE HARBOR': '5700',
+    'MUNDALLIO': '5700',
+    'PORT AUGUSTA': '5700',
+    'PORT AUGUSTA NORTH': '5700',
+    'PORT AUGUSTA WEST': '5700',
+    'PORT PATERSON': '5700',
+    'WAMI KATA': '5700',
+    'ARKAROOLA VILLAGE': '5710',
+    'COMMISSARIAT POINT': '5710',
+    'GLENDAMBO': '5710',
+    'KINGOONYA': '5710',
+    'NONNING': '5710',
+    'STIRLING NORTH': '5710',
+    'TARCOOLA': '5710',
+    'WINNINOWIE': '5710',
+    'WOOLUNDUNGA': '5710',
+    'PIMBA': '5720',
+    'WOOMERA': '5720',
+    'ANDAMOOKA': '5722',
+    'COOBER PEDY': '5723',
+    'MARLA': '5724',
+    'MINTABIE': '5724',
+    'OLYMPIC DAM': '5725',
+    'ROXBY DOWNS': '5725',
+    'BELTANA': '5730',
+    'BLINMAN': '5730',
+    'PARACHILNA': '5730',
+    'CORDILLO DOWNS': '5731',
+    'INNAMINCKA': '5731',
+    'MERTY MERTY': '5731',
+    'MOOLAWATANA': '5731',
+    'WITCHELINA': '5731',
+    'COPLEY': '5732',
+    'NEPABUNNA': '5732',
+    'FARINA': '5733',
+    'MARREE': '5733',
+    'OODNADATTA': '5734',
+    'ADELAIDE AIRPORT': '5950',
+    'EXPORT PARK': '5950',
+    'CITY DELIVERY CENTRE': '6000',
+    'PERTH': '6000',
+    'PERTH GPO': '6000',
+    'EAST PERTH': '6004',
+    'WEST PERTH': '6005',
+    'NORTH PERTH': '6006',
+    'LEEDERVILLE': '6007',
+    'WEST LEEDERVILLE': '6007',
+    'DAGLISH': '6008',
+    'SHENTON PARK': '6008',
+    'SUBIACO': '6008',
+    'SUBIACO EAST': '6008',
+    'BROADWAY NEDLANDS': '6009',
+    'CRAWLEY': '6009',
+    'DALKEITH': '6009',
+    'NEDLANDS': '6009',
+    'NEDLANDS DC': '6009',
+    'CLAREMONT': '6010',
+    'CLAREMONT NORTH': '6010',
+    'KARRAKATTA': '6010',
+    'MOUNT CLAREMONT': '6010',
+    'SWANBOURNE': '6010',
+    'COTTESLOE': '6011',
+    'PEPPERMINT GROVE': '6011',
+    'FLOREAT': '6014',
+    'FLOREAT FORUM': '6014',
+    'JOLIMONT': '6014',
+    'WEMBLEY': '6014',
+    'CITY BEACH': '6015',
+    'GLENDALOUGH': '6016',
+    'MOUNT HAWTHORN': '6016',
+    'HERDSMAN': '6017',
+    'OSBORNE PARK': '6017',
+    'OSBORNE PARK DC': '6017',
+    'CHURCHLANDS': '6018',
+    'DOUBLEVIEW': '6018',
+    'GWELUP': '6018',
+    'GWELUP DC': '6018',
+    'INNALOO': '6018',
+    'KARRINYUP': '6018',
+    'WEMBLEY DOWNS': '6019',
+    'CARINE': '6020',
+    'MARMION': '6020',
+    'WATERMANS BAY': '6020',
+    'BALCATTA': '6021',
+    'HAMERSLEY': '6022',
+    'DUNCRAIG': '6023',
+    'HILLARYS': '6025',
+    'KALLAROO': '6025',
+    'PADBURY': '6025',
+    'KINGSLEY': '6026',
+    'BELDON': '6027',
+    'CONNOLLY': '6027',
+    'EDGEWATER': '6027',
+    'HEATHRIDGE': '6027',
+    'JOONDALUP': '6027',
+    'JOONDALUP DC': '6027',
+    'MULLALOO': '6027',
+    'OCEAN REEF': '6027',
+    'BURNS BEACH': '6028',
+    'CURRAMBINE': '6028',
+    'KINROSS': '6028',
+    'TRIGG': '6029',
+    'CLARKSON': '6030',
+    'QUINNS ROCKS': '6030',
+    'TAMALA PARK': '6030',
+    'BANKSIA GROVE': '6031',
+    'NEERABUP': '6031',
+    'NOWERGUP': '6032',
+    'CARABOODA': '6033',
+    'YANCHEP': '6035',
+    'TWO ROCKS': '6037',
+    'ALKIMOS': '6038',
+    'CARABAN': '6041',
+    'GABBADAH': '6041',
+    'GUILDERTON': '6041',
+    'WILBINGA': '6041',
+    'SEABIRD': '6042',
+    'BRETON BAY': '6043',
+    'LEDGE POINT': '6043',
+    'KARAKIN': '6044',
+    'LANCELIN': '6044',
+    'NILGEN': '6044',
+    'WEDGE ISLAND': '6044',
+    'COOLBINIA': '6050',
+    'MENORA': '6050',
+    'MOUNT LAWLEY': '6050',
+    'BEDFORD': '6052',
+    'BASSENDEAN DC': '6054',
+    'EDEN HILL': '6054',
+    'KIARA': '6054',
+    'LOCKRIDGE': '6054',
+    'CAVERSHAM': '6055',
+    'HAZELMERE': '6055',
+    'HENLEY BROOK': '6055',
+    'SOUTH GUILDFORD': '6055',
+    'WEST SWAN': '6055',
+    'BASKERVILLE': '6056',
+    'BOYA': '6056',
+    'HELENA VALLEY': '6056',
+    'JANE BROOK': '6056',
+    'KOONGAMIA': '6056',
+    'MIDDLE SWAN': '6056',
+    'MIDLAND': '6056',
+    'MIDVALE': '6056',
+    'MILLENDON': '6056',
+    'STRATTON': '6056',
+    'SWAN VIEW': '6056',
+    'VIVEASH': '6056',
+    'WOODBRIDGE': '6056',
+    'HIGH WYCOMBE': '6057',
+    'MAIDA VALE': '6057',
+    'FORRESTFIELD': '6058',
+    'DIANELLA': '6059',
+    'DOG SWAMP': '6060',
+    'JOONDANNA': '6060',
+    'TUART HILL': '6060',
+    'YOKINE': '6060',
+    'BALGA': '6061',
+    'NOLLAMARA': '6061',
+    'WESTMINSTER': '6061',
+    'EMBLETON': '6062',
+    'MORLEY': '6062',
+    'NORANDA': '6062',
+    'BEECHBORO': '6063',
+    'ALEXANDER HEIGHTS': '6064',
+    'GIRRAWHEEN': '6064',
+    'KOONDOOLA': '6064',
+    'MARANGAROO': '6064',
+    'DARCH': '6065',
+    'GNANGARA': '6065',
+    'HOCKING': '6065',
+    'JANDABUP': '6065',
+    'KINGSWAY': '6065',
+    'LANDSDALE': '6065',
+    'LEXIA': '6065',
+    'MADELEY': '6065',
+    'MARIGINIUP': '6065',
+    'MELALEUCA': '6065',
+    'PEARSALL': '6065',
+    'PINJAR': '6065',
+    'SINAGRA': '6065',
+    'TAPPING': '6065',
+    'WANGARA': '6065',
+    'WANGARA DC': '6065',
+    'WANNEROO': '6065',
+    'BALLAJURA': '6066',
+    'CULLACABARDEE': '6067',
+    'WHITEMAN': '6068',
+    'AVELEY': '6069',
+    'BELHUS': '6069',
+    'BRIGADOON': '6069',
+    'ELLENBROOK': '6069',
+    'ELLENBROOK EAST': '6069',
+    'THE VINES': '6069',
+    'UPPER SWAN': '6069',
+    'GLEN FORREST': '6071',
+    'HOVEA': '6071',
+    'MAHOGANY CREEK': '6072',
+    'MUNDARING': '6073',
+    'MUNDARING DC': '6073',
+    'SAWYERS VALLEY': '6074',
+    'BICKLEY': '6076',
+    'CARMEL': '6076',
+    'GOOSEBERRY HILL': '6076',
+    'HACKETTS GULLY': '6076',
+    'KALAMUNDA': '6076',
+    'LESMURDIE': '6076',
+    'PAULLS VALLEY': '6076',
+    'PICKERING BROOK': '6076',
+    'PIESSE BROOK': '6076',
+    'WALLISTON': '6076',
+    'WALLISTON DC': '6076',
+    'PARKERVILLE': '6081',
+    'STONEVILLE': '6081',
+    'BAILUP': '6082',
+    'MOUNT HELENA': '6082',
+    'GIDGEGANNUP': '6083',
+    'MORANGUP': '6083',
+    'AVON VALLEY NATIONAL PARK': '6084',
+    'BULLSBROOK': '6084',
+    'CHITTERING': '6084',
+    'LOWER CHITTERING': '6084',
+    'WALYUNGA NATIONAL PARK': '6084',
+    'MALAGA': '6090',
+    'BURSWOOD': '6100',
+    'LATHLAIN': '6100',
+    'VICTORIA PARK': '6100',
+    'CARLISLE': '6101',
+    'CARLISLE NORTH': '6101',
+    'CARLISLE SOUTH': '6101',
+    'EAST VICTORIA PARK': '6101',
+    'BENTLEY DC': '6102',
+    'BENTLEY SOUTH': '6102',
+    'ST JAMES': '6102',
+    'RIVERVALE': '6103',
+    'CLOVERDALE': '6105',
+    'KEWDALE': '6105',
+    'PERTH AIRPORT': '6105',
+    'WELSHPOOL DC': '6106',
+    'BECKENHAM': '6107',
+    'CANNINGTON': '6107',
+    'EAST CANNINGTON': '6107',
+    'KENWICK': '6107',
+    'WILSON': '6107',
+    'THORNLIE': '6108',
+    'MADDINGTON': '6109',
+    'GOSNELLS': '6110',
+    'MARTIN': '6110',
+    'SOUTHERN RIVER': '6110',
+    'ASHENDON': '6111',
+    'CANNING MILLS': '6111',
+    'CHAMPION LAKES': '6111',
+    'KARRAGULLEN': '6111',
+    'KELMSCOTT': '6111',
+    'KELMSCOTT DC': '6111',
+    'LESLEY': '6111',
+    'ROLEYSTONE': '6111',
+    'WESTFIELD': '6111',
+    'BEDFORDALE': '6112',
+    'FORRESTDALE': '6112',
+    'HARRISDALE': '6112',
+    'MOUNT NASURA': '6112',
+    'MOUNT RICHON': '6112',
+    'PIARA WATERS': '6112',
+    'SEVILLE GROVE': '6112',
+    'WUNGONG': '6112',
+    'OAKFORD': '6121',
+    'OLDBURY': '6121',
+    'BYFORD': '6122',
+    'CARDUP': '6122',
+    'DARLING DOWNS': '6122',
+    'KARRAKUP': '6122',
+    'MUNDIJONG': '6123',
+    'WHITBY': '6123',
+    'JARRAHDALE': '6124',
+    'MARDELLA': '6125',
+    'KEYSBROOK': '6126',
+    'LANGFORD': '6147',
+    'ROSSMOYNE': '6148',
+    'LEEMING': '6149',
+    'BATEMAN': '6150',
+    'MURDOCH': '6150',
+    'WINTHROP': '6150',
+    'SOUTH PERTH': '6151',
+    'SOUTH PERTH ANGELO ST': '6151',
+    'KARAWARA': '6152',
+    'MANNING': '6152',
+    'SALTER POINT': '6152',
+    'APPLECROSS': '6153',
+    'APPLECROSS NORTH': '6153',
+    'ARDROSS': '6153',
+    'CANNING BRIDGE APPLECROSS': '6153',
+    'ALFRED COVE': '6154',
+    'BOORAGOON': '6154',
+    'MYAREE': '6154',
+    'CANNING VALE': '6155',
+    'CANNING VALE DC': '6155',
+    'CANNING VALE EAST': '6155',
+    'CANNING VALE SOUTH': '6155',
+    'WILLETTON': '6155',
+    'ATTADALE': '6156',
+    'WILLAGEE': '6156',
+    'WILLAGEE CENTRAL': '6156',
+    'BICTON': '6157',
+    'PALMYRA DC': '6157',
+    'EAST FREMANTLE': '6158',
+    'NORTH FREMANTLE': '6159',
+    'FREMANTLE': '6160',
+    'ROTTNEST ISLAND': '6161',
+    'SOUTH FREMANTLE': '6162',
+    'WHITE GUM VALLEY': '6162',
+    'BIBRA LAKE': '6163',
+    'BIBRA LAKE DC': '6163',
+    'COOLBELLUP': '6163',
+    'HAMILTON HILL': '6163',
+    'KARDINYA': '6163',
+    'NORTH COOGEE': '6163',
+    'NORTH LAKE': '6163',
+    'SAMSON': '6163',
+    'SPEARWOOD': '6163',
+    'ATWELL': '6164',
+    'AUBIN GROVE': '6164',
+    'BANJUP': '6164',
+    'BEELIAR': '6164',
+    'COCKBURN CENTRAL': '6164',
+    'HAMMOND PARK': '6164',
+    'JANDAKOT': '6164',
+    'SOUTH LAKE': '6164',
+    'SUCCESS': '6164',
+    'YANGEBUP': '6164',
+    'NAVAL BASE': '6165',
+    'HENDERSON': '6166',
+    'MUNSTER': '6166',
+    'WATTLEUP': '6166',
+    'ANKETELL': '6167',
+    'BERTRAM': '6167',
+    'CALISTA': '6167',
+    'KWINANA BEACH': '6167',
+    'KWINANA TOWN CENTRE': '6167',
+    'MANDOGALUP': '6167',
+    'MEDINA': '6167',
+    'ORELIA': '6167',
+    'PARMELIA': '6167',
+    'POSTANS': '6167',
+    'THE SPECTACLES': '6167',
+    'WANDI': '6167',
+    'COOLOONGUP': '6168',
+    'EAST ROCKINGHAM': '6168',
+    'GARDEN ISLAND': '6168',
+    'HILLMAN': '6168',
+    'PERON': '6168',
+    'ROCKINGHAM BEACH': '6168',
+    'ROCKINGHAM DC': '6168',
+    'SAFETY BAY': '6169',
+    'WAIKIKI': '6169',
+    'WARNBRO': '6169',
+    'LEDA': '6170',
+    'WELLARD': '6170',
+    'BALDIVIS': '6171',
+    'PORT KENNEDY': '6172',
+    'SECRET HARBOUR': '6173',
+    'GOLDEN BAY': '6174',
+    'KARNUP': '6176',
+    'STAKE HILL': '6181',
+    'KERALUP': '6182',
+    'MYARA': '6207',
+    'NAMBEELUP': '6207',
+    'NORTH DANDALUP': '6207',
+    'SOLUS': '6207',
+    'WHITTAKER': '6207',
+    'BLYTHEWOOD': '6208',
+    'FAIRBRIDGE': '6208',
+    'MEELON': '6208',
+    'NIRIMBA': '6208',
+    'NORTH YUNDERUP': '6208',
+    'OAKLEY': '6208',
+    'PINJARRA': '6208',
+    'POINT GREY': '6208',
+    'SOUTH YUNDERUP': '6208',
+    'WEST PINJARRA': '6208',
+    'BARRAGUP': '6209',
+    'FURNISSDALE': '6209',
+    'COODANUP': '6210',
+    'FALCON': '6210',
+    'GREENFIELDS': '6210',
+    'HALLS HEAD': '6210',
+    'MADORA BAY': '6210',
+    'MANDURAH': '6210',
+    'MANDURAH DC': '6210',
+    'MANDURAH EAST': '6210',
+    'MANDURAH NORTH': '6210',
+    'MEADOW SPRINGS': '6210',
+    'WANNANUP': '6210',
+    'BOUVARD': '6211',
+    'DAWESVILLE': '6211',
+    'HERRON': '6211',
+    'BANKSIADALE': '6213',
+    'DWELLINGUP': '6213',
+    'ETMILYN': '6213',
+    'HOLYOAKE': '6213',
+    'INGLEHOPE': '6213',
+    'MARRINUP': '6213',
+    'BIRCHMONT': '6214',
+    'COOLUP': '6214',
+    'WEST COOLUP': '6214',
+    'HAMEL': '6215',
+    'LAKE CLIFTON': '6215',
+    'NANGA BROOK': '6215',
+    'PRESTON BEACH': '6215',
+    'WAGERUP': '6215',
+    'WAROONA': '6215',
+    'YARLOOP': '6218',
+    'COOKERNUP': '6220',
+    'HARVEY': '6220',
+    'HOFFMAN': '6220',
+    'MYALUP': '6220',
+    'UDUC': '6220',
+    'WARAWARRUP': '6220',
+    'WOKALUP': '6221',
+    'BENGER': '6223',
+    'BEELA': '6224',
+    'ALLANSON': '6225',
+    'BOWELLING': '6225',
+    'COLLIE BURN': '6225',
+    'HARRIS RIVER': '6225',
+    'LYALLS MILL': '6225',
+    'MCALINDEN': '6225',
+    'MUJA': '6225',
+    'MUMBALLUP': '6225',
+    'MUNGALUP': '6225',
+    'NOGGERUP': '6225',
+    'PRESTON SETTLEMENT': '6225',
+    'SHOTTS': '6225',
+    'WORSLEY': '6225',
+    'YOURDAMUNG LAKE': '6225',
+    'ROELANDS': '6226',
+    'BUREKUP': '6227',
+    'PICTON EAST': '6229',
+    'CAREY PARK': '6230',
+    'COLLEGE GROVE': '6230',
+    'DALYELLUP': '6230',
+    'EAST BUNBURY': '6230',
+    'GELORUP': '6230',
+    'SOUTH BUNBURY': '6230',
+    'USHER': '6230',
+    'WITHERS': '6230',
+    'MILLBRIDGE': '6232',
+    'AUSTRALIND': '6233',
+    'BINNINGUP': '6233',
+    'LESCHENAULT': '6233',
+    'PARKFIELD': '6233',
+    'CROOKED BROOK': '6236',
+    'DARDANUP': '6236',
+    'DARDANUP WEST': '6236',
+    'WELLINGTON FOREST': '6236',
+    'WELLINGTON MILL': '6236',
+    'BOYANUP': '6237',
+    'ELGIN': '6237',
+    'GWINDINUP': '6237',
+    'NORTH BOYANUP': '6237',
+    'STRATHAM': '6237',
+    'THE PLAINS': '6237',
+    'BEELERUP': '6239',
+    'BROOKHAMPTON': '6239',
+    'CHARLEY CREEK': '6239',
+    'GLEN MERVYN': '6239',
+    'PAYNEDALE': '6239',
+    'QUEENWOOD': '6239',
+    'THOMSON BROOK': '6239',
+    'UPPER CAPEL': '6239',
+    'YABBERUP': '6239',
+    'LOWDEN': '6240',
+    'WILGA': '6243',
+    'WILGA WEST': '6243',
+    'BOYUP BROOK': '6244',
+    'CHOWERUP': '6244',
+    'DINNINUP': '6244',
+    'KULIKUP': '6244',
+    'MAYANUP': '6244',
+    'SCOTTS BROOK': '6244',
+    'TONEBRIDGE': '6244',
+    'BRAZIER': '6251',
+    'KIRUP': '6251',
+    'MULLALYUP': '6252',
+    'BALINGUP': '6253',
+    'GRIMWADE': '6253',
+    'GREENBUSHES': '6254',
+    'NORTH GREENBUSHES': '6254',
+    'BENJINUP': '6255',
+    'BRIDGETOWN': '6255',
+    'CATTERICK': '6255',
+    'HESTER': '6255',
+    'HESTER BROOK': '6255',
+    'KANGAROO GULLY': '6255',
+    'WINNEJUP': '6255',
+    'GLENLYNN': '6256',
+    'MARANUP': '6256',
+    'WANDILLUP': '6256',
+    'YORNUP': '6256',
+    'BALBARRUP': '6258',
+    'CROWEA': '6258',
+    'DEANMILL': '6258',
+    'DIAMOND TREE': '6258',
+    'DINGUP': '6258',
+    'DIXVALE': '6258',
+    'DONNELLY RIVER': '6258',
+    'GLENORAN': '6258',
+    'JARDEE': '6258',
+    'LAKE MUIR': '6258',
+    'LINFARNE': '6258',
+    'MANJIMUP': '6258',
+    'MIDDLESEX': '6258',
+    'MORDALUP': '6258',
+    'PALGARUP': '6258',
+    'PERUP': '6258',
+    'QUINNINUP': '6258',
+    'RINGBARK': '6258',
+    'SMITH BROOK': '6258',
+    'UPPER WARREN': '6258',
+    'WILGARRUP': '6258',
+    'YANMAH': '6258',
+    'BEEDELUP': '6260',
+    'BIDDELIA': '6260',
+    'CALLCUP': '6260',
+    'CHANNYBEARUP': '6260',
+    'COLLINS': '6260',
+    'EASTBROOK': '6260',
+    'LAKE JASPER': '6260',
+    'PEERABEELUP': '6260',
+    'PEMBERTON': '6260',
+    'YEAGARUP': '6260',
+    'BOORARA BROOK': '6262',
+    'MEERUP': '6262',
+    'NORTHCLIFFE': '6262',
+    'SHANNON': '6262',
+    'WINDY HARBOUR': '6262',
+    'CAPEL': '6271',
+    'CAPEL RIVER': '6271',
+    'PEPPERMINT GROVE BEACH': '6271',
+    'STIRLING ESTATE': '6271',
+    'BARRABUP': '6275',
+    'CARLOTTA': '6275',
+    'CUNDINUP': '6275',
+    'DARRADUP': '6275',
+    'EAST NANNUP': '6275',
+    'JALBARRAGUP': '6275',
+    'JARRAHWOOD': '6275',
+    'NANNUP': '6275',
+    'SCOTT RIVER EAST': '6275',
+    'YOGANUP': '6275',
+    'ABBA RIVER': '6280',
+    'ABBEY': '6280',
+    'ACTON PARK': '6280',
+    'AMBERGATE': '6280',
+    'ANNIEBROOK': '6280',
+    'BOALLIA': '6280',
+    'BOVELL': '6280',
+    'BUSSELTON': '6280',
+    'CARBUNUP RIVER': '6280',
+    'CHAPMAN HILL': '6280',
+    'GEOGRAPHE': '6280',
+    'HITHERGREEN': '6280',
+    'JINDONG': '6280',
+    'KALGUP': '6280',
+    'KALOORUP': '6280',
+    'KEALY': '6280',
+    'LUDLOW': '6280',
+    'MARYBROOK': '6280',
+    'METRICUP': '6280',
+    'NORTH JINDONG': '6280',
+    'REINSCOURT': '6280',
+    'RUABON': '6280',
+    'SABINA RIVER': '6280',
+    'SIESTA PARK': '6280',
+    'TUTUNUP': '6280',
+    'VASSE': '6280',
+    'WALSALL': '6280',
+    'WEST BUSSELTON': '6280',
+    'WILYABRUP': '6280',
+    'WONNERUP': '6280',
+    'YALYALUP': '6280',
+    'YELVERTON': '6280',
+    'YOONGARILLUP': '6280',
+    'DUNSBOROUGH': '6281',
+    'EAGLE BAY': '6281',
+    'NATURALISTE': '6281',
+    'QUEDJINUP': '6281',
+    'QUINDALUP': '6281',
+    'YALLINGUP': '6282',
+    'YALLINGUP SIDING': '6282',
+    'BAUDIN': '6284',
+    'COWARAMUP': '6284',
+    'GRACETOWN': '6284',
+    'TREETON': '6284',
+    'BRAMLEY': '6285',
+    'GNARABUP': '6285',
+    'MARGARET RIVER': '6285',
+    'OSMINGTON': '6285',
+    'PREVELLY': '6285',
+    'ROSA BROOK': '6285',
+    'ROSA GLEN': '6285',
+    'SCHROEDER': '6285',
+    'BORANUP': '6286',
+    'WITCHCLIFFE': '6286',
+    'ALEXANDRA BRIDGE': '6288',
+    'COURTENAY': '6288',
+    'HAMELIN BAY': '6288',
+    'KARRIDALE': '6288',
+    'NILLUP': '6288',
+    'SCOTT RIVER': '6288',
+    'WARNER GLEN': '6288',
+    'AUGUSTA': '6290',
+    'DEEPDENE': '6290',
+    'EAST AUGUSTA': '6290',
+    'KUDARDUP': '6290',
+    'LEEUWIN': '6290',
+    'MOLLOY ISLAND': '6290',
+    'BADGIN': '6302',
+    'BALLADONG': '6302',
+    'BURGES': '6302',
+    'CALJIE': '6302',
+    'COLD HARBOUR': '6302',
+    'DALIAK': '6302',
+    'FLINT': '6302',
+    'GILGERING': '6302',
+    'GREENHILLS': '6302',
+    'GWAMBYGINE': '6302',
+    'INKPEN': '6302',
+    'KAURING': '6302',
+    'MALEBELLING': '6302',
+    'MOUNT HARDEY': '6302',
+    'NARRALOGGAN': '6302',
+    'QUELLINGTON': '6302',
+    'ST RONANS': '6302',
+    'TALBOT WEST': '6302',
+    'YORK': '6302',
+    'BALLY BALLY': '6304',
+    'DALE': '6304',
+    'EAST BEVERLEY': '6304',
+    'KOKEBY': '6304',
+    'MORBINNING': '6304',
+    'ALDERSYDE': '6306',
+    'BROOKTON': '6306',
+    'BULYEE': '6306',
+    'JELCOBINE': '6306',
+    'KWEDA': '6306',
+    'CODJATOTINE': '6308',
+    'DWARDA': '6308',
+    'EAST PINGELLY': '6308',
+    'GILLIMANNING': '6308',
+    'PINGELLY': '6308',
+    'PUMPHREYS BRIDGE': '6308',
+    'SPRINGS': '6308',
+    'WANDERING': '6308',
+    'WEST PINGELLY': '6308',
+    'EAST POPANYINNING': '6309',
+    'POPANYINNING': '6309',
+    'STRATHERNE': '6309',
+    'WEST POPANYINNING': '6309',
+    'COMMODINE': '6311',
+    'CONTINE': '6311',
+    'CUBALLING': '6311',
+    'DRYANDRA': '6311',
+    'LOL GRAY': '6311',
+    'TOWNSENDALE': '6311',
+    'WARDERING': '6311',
+    'YORNANING': '6311',
+    'BOUNDAIN': '6312',
+    'DUMBERNING': '6312',
+    'MINIGIN': '6312',
+    'NARROGIN': '6312',
+    'NARROGIN VALLEY': '6312',
+    'NOMANS LAKE': '6312',
+    'TOOLIBIN': '6312',
+    'YILLIMINNING': '6312',
+    'ARTHUR RIVER': '6315',
+    'BALLAYING': '6315',
+    'CANCANNING': '6315',
+    'COLLANILLING': '6315',
+    'DONGOLOCKING': '6315',
+    'GUNDARING': '6315',
+    'JALORAN': '6315',
+    'LIME LAKE': '6315',
+    'MINDING': '6315',
+    'PIESSEVILLE': '6315',
+    'WAGIN': '6315',
+    'WEDGECARRUP': '6315',
+    'BOYERINE': '6316',
+    'CARTMETICUP': '6316',
+    'WOODANILLING': '6316',
+    'BADGEBUP': '6317',
+    'BULLOCK HILLS': '6317',
+    'CARROLUP': '6317',
+    'COBLININE': '6317',
+    'COYRECUP': '6317',
+    'DATATINE': '6317',
+    'EWLYAMARTUP': '6317',
+    'KATANNING': '6317',
+    'MARRACOONDA': '6317',
+    'MOOJEBING': '6317',
+    'MURDONG': '6317',
+    'PINWERNYING': '6317',
+    'SOUTH DATATINE': '6317',
+    'SOUTH GLENCOE': '6317',
+    'BROOMEHILL': '6318',
+    'BROOMEHILL EAST': '6318',
+    'BROOMEHILL VILLAGE': '6318',
+    'BROOMEHILL WEST': '6318',
+    'BOBALONG': '6320',
+    'BORDERDALE': '6320',
+    'DARTNALL': '6320',
+    'LAKE TOOLBRUNUP': '6320',
+    'MOONIES HILL': '6320',
+    'TAMBELLUP': '6320',
+    'WANSBROUGH': '6320',
+    'KENDENUP': '6323',
+    'DENBARKER': '6324',
+    'PERILLUP': '6324',
+    'PORONGURUP': '6324',
+    'SOUTH STIRLING': '6324',
+    'TAKALARUP': '6324',
+    'WOOGENELLUP': '6324',
+    'NARRIKUP': '6326',
+    'REDMOND': '6327',
+    'REDMOND WEST': '6327',
+    'CHEYNES': '6328',
+    'GNOWELLEN': '6328',
+    'GREEN RANGE': '6328',
+    'KOJANEERUP SOUTH': '6328',
+    'MANYPEAKS': '6328',
+    'METTLER': '6328',
+    'WELLSTEAD': '6328',
+    'ALBANY': '6330',
+    'BAYONET HEAD': '6330',
+    'BIG GROVE': '6330',
+    'BORNHOLM': '6330',
+    'COLLINGWOOD HEIGHTS': '6330',
+    'CUTHBERT': '6330',
+    'DROME': '6330',
+    'ELLEKER': '6330',
+    'EMU POINT': '6330',
+    'FRENCHMAN BAY': '6330',
+    'GLEDHOW': '6330',
+    'GOODE BEACH': '6330',
+    'KALGAN': '6330',
+    'KING RIVER': '6330',
+    'KRONKUP': '6330',
+    'LANGE': '6330',
+    'LITTLE GROVE': '6330',
+    'LOWER KING': '6330',
+    'LOWLANDS': '6330',
+    'MARBELUP': '6330',
+    'MCKAIL': '6330',
+    'MIDDLETON BEACH': '6330',
+    'MILPARA': '6330',
+    'MIRA MAR': '6330',
+    'MOUNT CLARENCE': '6330',
+    'MOUNT ELPHINSTONE': '6330',
+    'MOUNT MELVILLE': '6330',
+    'NANARUP': '6330',
+    'NAPIER': '6330',
+    'NULLAKI': '6330',
+    'ORANA': '6330',
+    'PORT ALBANY': '6330',
+    'SANDPATCH': '6330',
+    'SEPPINGS': '6330',
+    'SPENCER PARK': '6330',
+    'TORBAY': '6330',
+    'TORNDIRRUP': '6330',
+    'VANCOUVER PENINSULA': '6330',
+    'WALMSLEY': '6330',
+    'WARRENUP': '6330',
+    'WEST CAPE HOWE': '6330',
+    'WILLYUNG': '6330',
+    'YAKAMIA': '6330',
+    'YOUNGS SIDING': '6330',
+    'BOW BRIDGE': '6333',
+    'DENMARK': '6333',
+    'HAZELVALE': '6333',
+    'KENTDALE': '6333',
+    'KORDABUP': '6333',
+    'MOUNT ROMANCE': '6333',
+    'NORNALUP': '6333',
+    'OCEAN BEACH': '6333',
+    'PARRYVILLE': '6333',
+    'PEACEFUL BAY': '6333',
+    'SCOTSDALE': '6333',
+    'TINGLEDALE': '6333',
+    'TRENT': '6333',
+    'WILLIAM BAY': '6333',
+    'GNOWANGERUP': '6335',
+    'JACKITUP': '6335',
+    'KEBARINGUP': '6335',
+    'PALLINUP': '6335',
+    'COWALELLUP': '6336',
+    'MILLS LAKE': '6336',
+    'MINDARABIN': '6336',
+    'NEEDILUP': '6336',
+    'ONGERUP': '6336',
+    'TOOMPUP': '6336',
+    'FITZGERALD': '6337',
+    'GAIRDNER': '6337',
+    'JACUP': '6337',
+    'JERRAMUNGUP': '6337',
+    'WEST FITZGERALD': '6337',
+    'AMELUP': '6338',
+    'BORDEN': '6338',
+    'BOXWOOD HILL': '6338',
+    'BREMER BAY': '6338',
+    'MAGITUP': '6338',
+    'MONJEBUP': '6338',
+    'NALYERLUP': '6338',
+    'NORTH STIRLINGS': '6338',
+    'STIRLING RANGE NATIONAL PARK': '6338',
+    'NYABING': '6341',
+    'PINGRUP': '6343',
+    'FITZGERALD RIVER NATIONAL PARK': '6346',
+    'JERDACUTTUP': '6346',
+    'RAVENSTHORPE': '6346',
+    'WEST RIVER': '6346',
+    'DUMBLEYUNG': '6350',
+    'NAIRIBIN': '6350',
+    'NIPPERING': '6350',
+    'MOULYINNING': '6351',
+    'NORTH MOULYINNING': '6351',
+    'KUKERIN': '6352',
+    'MERILUP': '6352',
+    'NORTH KUKERIN': '6352',
+    'SOUTH KUKERIN': '6352',
+    'BEENONG': '6353',
+    'BUNICHE': '6353',
+    'KUENDER': '6353',
+    'LAKE GRACE': '6353',
+    'MALLEE HILL': '6353',
+    'NEENDALING': '6353',
+    'NORTH BURNGUP': '6353',
+    'NORTH LAKE GRACE': '6353',
+    'SOUTH LAKE GRACE': '6353',
+    'TARIN ROCK': '6353',
+    'DUNN ROCK': '6355',
+    'EAST NEWDEGATE': '6355',
+    'HOLT ROCK': '6355',
+    'LAKE BIDDY': '6355',
+    'LAKE CAMM': '6355',
+    'LITTLE ITALY': '6355',
+    'NEWDEGATE': '6355',
+    'SOUTH NEWDEGATE': '6355',
+    'VARLEY': '6355',
+    'LAKE KING': '6356',
+    'MOUNT MADDEN': '6356',
+    'PINGARING': '6357',
+    'KARLGARIN': '6358',
+    'FORRESTANIA': '6359',
+    'HYDEN': '6359',
+    'HARRISMITH': '6361',
+    'TINCURRIN': '6361',
+    'DUDININ': '6363',
+    'WALYURIN': '6363',
+    'JILAKIN': '6365',
+    'JITARNING': '6365',
+    'KULIN': '6365',
+    'KULIN WEST': '6365',
+    'KONDININ': '6367',
+    'SOUTH KUMMININ': '6368',
+    'NAREMBEEN': '6369',
+    'WADDERIN': '6369',
+    'WEST HOLLETON': '6369',
+    'WOOLOCUTTY': '6369',
+    'EAST WICKEPIN': '6370',
+    'KIRK ROCK': '6370',
+    'MALYALLING': '6370',
+    'WICKEPIN': '6370',
+    'WOGOLIN': '6370',
+    'YEALERING': '6372',
+    'BULLARING': '6373',
+    'ADAMSVALE': '6375',
+    'BILBARIN': '6375',
+    'CORRIGIN': '6375',
+    'GORGE ROCK': '6375',
+    'KUNJIN': '6375',
+    'KURRENKUTTEN': '6375',
+    'BADJALING': '6383',
+    'BALKULING': '6383',
+    'CUBBINE': '6383',
+    'DANGIN': '6383',
+    'DOODENANNING': '6383',
+    'DULBELLING': '6383',
+    'MOUNT STIRLING': '6383',
+    'QUAIRADING': '6383',
+    'SOUTH QUAIRADING': '6383',
+    'WAMENUSKING': '6383',
+    'YOTING': '6383',
+    'PANTAPIN': '6384',
+    'KWOLYIN': '6385',
+    'SHACKLETON': '6386',
+    'BODDINGTON': '6390',
+    'CROSSMAN': '6390',
+    'LOWER HOTHAM': '6390',
+    'MARRADONG': '6390',
+    'MOUNT COOKE': '6390',
+    'MOUNT WELLS': '6390',
+    'NORTH BANNISTER': '6390',
+    'RANFORD': '6390',
+    'UPPER MURRAY': '6390',
+    'WURAMING': '6390',
+    'QUINDANNING': '6391',
+    'WILLIAMS': '6391',
+    'BOKAL': '6392',
+    'BOOLADING': '6392',
+    'DARDADINE': '6392',
+    'DARKAN': '6392',
+    'MEEKING': '6392',
+    'CORDERING': '6393',
+    'DURANILLIN': '6393',
+    'MOODIARRUP': '6393',
+    'TRIGWELL': '6393',
+    'BEAUFORT RIVER': '6394',
+    'BOILUP': '6394',
+    'BOSCABEL': '6394',
+    'CHANGERUP': '6394',
+    'MURADUP': '6394',
+    'ORCHID VALLEY': '6394',
+    'QUALEUP': '6394',
+    'CHERRY TREE POOL': '6395',
+    'JINGALUP': '6395',
+    'KOJONUP': '6395',
+    'MOBRUP': '6395',
+    'RYANSBROOK': '6395',
+    'FRANKLAND RIVER': '6396',
+    'NORTH WALPOLE': '6398',
+    'WALPOLE': '6398',
+    'BURLONG': '6401',
+    'CUNJARDINE': '6401',
+    'JENNACUBBINE': '6401',
+    'JENNAPULLIN': '6401',
+    'MALABAINE': '6401',
+    'MEENAAR': '6401',
+    'MOKINE': '6401',
+    'MULUCKINE': '6401',
+    'MUMBERKINE': '6401',
+    'MURESK': '6401',
+    'NORTHAM': '6401',
+    'SOUTHERN BROOK': '6401',
+    'SPENCERS BROOK': '6401',
+    'THROSSELL': '6401',
+    'WONGAMINE': '6401',
+    'GRASS VALLEY': '6403',
+    'GREENWOODS VALLEY': '6405',
+    'MECKERING': '6405',
+    'QUELAGETTING': '6405',
+    'WARDING EAST': '6405',
+    'CUNDERDIN': '6407',
+    'WAEEL': '6407',
+    'WATERCARRIN': '6407',
+    'WYOLA WEST': '6407',
+    'YOUNDEGIN': '6407',
+    'NORTH TAMMIN': '6409',
+    'SOUTH TAMMIN': '6409',
+    'TAMMIN': '6409',
+    'DAADENNING CREEK': '6410',
+    'KELLERBERRIN': '6410',
+    'MOUNT CAROLINE': '6410',
+    'NORTH KELLERBERRIN': '6410',
+    'DOODLAKINE': '6411',
+    'SOUTH DOODLAKINE': '6411',
+    'BAANDEE': '6412',
+    'NORTH BAANDEE': '6412',
+    'HINES HILL': '6413',
+    'NANGEENAN': '6414',
+    'GOOMARIN': '6415',
+    'KORBEL': '6415',
+    'MERREDIN': '6415',
+    'NOKANING': '6415',
+    'NORPA': '6415',
+    'TANDEGIN': '6415',
+    'BRUCE ROCK': '6418',
+    'ARDATH': '6419',
+    'CRAMPHORNE': '6420',
+    'MUNTADGIN': '6420',
+    'BURRACOPPIN': '6421',
+    'SOUTH BURRACOPPIN': '6421',
+    'WARRALAKIN': '6421',
+    'WALGOOLAN': '6422',
+    'BOODAROCKIN': '6423',
+    'CARRABIN': '6423',
+    'WARRACHUPPIN': '6423',
+    'WESTONIA': '6423',
+    'BODALLIN': '6424',
+    'NORTH BODALLIN': '6424',
+    'SOUTH BODALLIN': '6424',
+    'DULYALBIN': '6425',
+    'MOORINE ROCK': '6425',
+    'CORINTHIA': '6426',
+    'GHOOLI': '6426',
+    'HOLLETON': '6426',
+    'MARVEL LOCH': '6426',
+    'MOUNT HAMPTON': '6426',
+    'MOUNT HOLLAND': '6426',
+    'MOUNT JACKSON': '6426',
+    'MOUNT PALMER': '6426',
+    'PARKER RANGE': '6426',
+    'SKELETON ROCK': '6426',
+    'SOUTH YILGARN': '6426',
+    'TURKEY HILL': '6426',
+    'YELLOWDINE': '6426',
+    'KOOLYANOBBING': '6427',
+    'BABAKIN': '6428',
+    'BOORABBIN': '6429',
+    'BULLABULLING': '6429',
+    'KARRAMINDIE': '6429',
+    'MOUNT BURGES': '6429',
+    'VICTORIA ROCK': '6429',
+    'BINDULI': '6430',
+    'BROADWOOD': '6430',
+    'HANNANS': '6430',
+    'KALGOORLIE': '6430',
+    'KARLKURLA': '6430',
+    'MULLINGAR': '6430',
+    'SOUTH KALGOORLIE': '6430',
+    'WEST KALGOORLIE': '6430',
+    'WEST LAMINGTON': '6430',
+    'YILKARI': '6430',
+    'BOORARA': '6431',
+    'BULONG': '6431',
+    'FEYSVILLE': '6431',
+    'KANOWNA': '6431',
+    'KOOKYNIE': '6431',
+    'KURNALPI': '6431',
+    'ORA BANDA': '6431',
+    'PLUMRIDGE LAKES': '6431',
+    'BOULDER': '6432',
+    'FIMISTON': '6432',
+    'SOUTH BOULDER': '6432',
+    'CUNDEELEE': '6434',
+    'PARKESTON': '6434',
+    'RAWLINNA': '6434',
+    'ZANTHUS': '6434',
+    'ULARRING': '6436',
+    'LEINSTER': '6437',
+    'SIR SAMUEL': '6437',
+    'LAKE DARLOT': '6438',
+    'LEONORA': '6438',
+    'BANDYA': '6440',
+    'BEADELL': '6440',
+    'COSMO NEWBERY': '6440',
+    'LAKE WELLS': '6440',
+    'NEALE': '6440',
+    'KAMBALDA EAST': '6442',
+    'KAMBALDA WEST': '6442',
+    'BALLADONIA': '6443',
+    'CAIGUNA': '6443',
+    'COCKLEBIDDY': '6443',
+    'EUCLA': '6443',
+    'FRASER RANGE': '6443',
+    'HIGGINSVILLE': '6443',
+    'MADURA': '6443',
+    'MUNDRABILLA': '6443',
+    'NORSEMAN': '6443',
+    'WIDGIEMOOLTHA': '6443',
+    'NORTH CASCADE': '6445',
+    'SALMON GUMS': '6445',
+    'GRASS PATCH': '6446',
+    'LORT RIVER': '6447',
+    'MOUNT NEY': '6447',
+    'SCADDAN': '6447',
+    'WITTENOOM HILLS': '6447',
+    'GIBSON': '6448',
+    'BANDY CREEK': '6450',
+    'BOYATUP': '6450',
+    'CAPE LE GRAND': '6450',
+    'CASTLETOWN': '6450',
+    'CHADWICK': '6450',
+    'CONDINGUP': '6450',
+    'COOMALBIDGUP': '6450',
+    'DALYUP': '6450',
+    'EAST MUNGLINUP': '6450',
+    'ESPERANCE': '6450',
+    'MERIVALE': '6450',
+    'MONJINGUP': '6450',
+    'MUNGLINUP': '6450',
+    'MYRUP': '6450',
+    'NERIDUP': '6450',
+    'NULSEN': '6450',
+    'PINK LAKE': '6450',
+    'SINCLAIR': '6450',
+    'WINDABOUT': '6450',
+    'BURAMINYA': '6452',
+    'CAPE ARID': '6452',
+    'ISRAELITE BAY': '6452',
+    'GOOMALLING': '6460',
+    'HULONGINE': '6460',
+    'KARRANADGIN': '6460',
+    'UCARTY WEST': '6460',
+    'WALYORMOURING': '6460',
+    'DOWERIN': '6461',
+    'KOOMBERKINE': '6461',
+    'MINNIVALE': '6462',
+    'UCARTY': '6462',
+    'BENJABERRING': '6463',
+    'MANMANNING': '6465',
+    'CADOUX': '6466',
+    'BURAKIN': '6467',
+    'GOODLANDS': '6468',
+    'KALANNIE': '6468',
+    'PETRUDOR': '6468',
+    'KULJA': '6470',
+    'BEACON': '6472',
+    'BIMBIJY': '6472',
+    'CLEARY': '6472',
+    'KARROUN HILL': '6472',
+    'MOUROUBRA': '6472',
+    'REMLAP': '6472',
+    'TAMPU': '6472',
+    'NORTH WIALKI': '6473',
+    'WIALKI': '6473',
+    'BADGERIN ROCK': '6475',
+    'BOORALAMING': '6475',
+    'DUKIN': '6475',
+    'KOORDA': '6475',
+    'LAKE MARGARETTE': '6475',
+    'MOLLERIN': '6475',
+    'NEWCARLBEON': '6475',
+    'GABBIN': '6476',
+    'BENCUBBIN': '6477',
+    'WELBUNGIN': '6477',
+    'BARBALIN': '6479',
+    'BONNIE ROCK': '6479',
+    'DANDANNING': '6479',
+    'ELACHBUTTING': '6479',
+    'KARLONING': '6479',
+    'LAKE BROWN': '6479',
+    'MUKINBUDIN': '6479',
+    'WATTONING': '6479',
+    'WILGOYNE': '6479',
+    'NUKARNI': '6480',
+    'BULLFINCH': '6484',
+    'ENNUIN': '6484',
+    'LAKE DEBORAH': '6484',
+    'COWCOWING': '6485',
+    'KORRELOCKING': '6485',
+    'NALKAIN': '6485',
+    'NEMBUDDING': '6485',
+    'WYALKATCHEM': '6485',
+    'NORTH YELBENI': '6487',
+    'SOUTH YELBENI': '6487',
+    'YELBENI': '6487',
+    'NORTH TRAYNING': '6488',
+    'SOUTH TRAYNING': '6488',
+    'TRAYNING': '6488',
+    'KUNUNOPPIN': '6489',
+    'NORTH KUNUNOPPIN': '6489',
+    'SOUTH KUNUNOPPIN': '6489',
+    'BURRAN ROCK': '6490',
+    'ELABBIN': '6490',
+    'KWELKAN': '6490',
+    'NUNGARIN': '6490',
+    'TALGOMINE': '6490',
+    'MUCHEA': '6501',
+    'BINDOON': '6502',
+    'BINDOON TRAINING AREA': '6502',
+    'BAMBUN': '6503',
+    'BEERMULLAH': '6503',
+    'BOONANARRING': '6503',
+    'BREERA': '6503',
+    'COONABIDGEE': '6503',
+    'COWALLA': '6503',
+    'CULLALLA': '6503',
+    'GINGIN': '6503',
+    'GINGINUP': '6503',
+    'LENNARD BROOK': '6503',
+    'MINDARRA': '6503',
+    'MOONDAH': '6503',
+    'MOORE RIVER NATIONAL PARK': '6503',
+    'MUCKENBURRA': '6503',
+    'NEERGABBY': '6503',
+    'ORANGE SPRINGS': '6503',
+    'RED GULLY': '6503',
+    'WANERIE': '6503',
+    'YEAL': '6503',
+    'MOOLIABEENEE': '6504',
+    'WANNAMAL': '6505',
+    'MOGUMBER': '6506',
+    'CATABY': '6507',
+    'COOLJARLOO': '6507',
+    'DANDARAGAN': '6507',
+    'MIMEGARRA': '6507',
+    'REGANS FORD': '6507',
+    'YATHROO': '6507',
+    'GLENTROMIE': '6509',
+    'NEW NORCIA': '6509',
+    'WADDINGTON': '6509',
+    'YARAWINDAH': '6509',
+    'BARBERTON': '6510',
+    'BERKSHIRE VALLEY': '6510',
+    'GILLINGARRA': '6510',
+    'KOOJAN': '6510',
+    'WALEBING': '6510',
+    'CERVANTES': '6511',
+    'COOMBERDALE': '6512',
+    'NAMBAN': '6512',
+    'GUNYIDI': '6513',
+    'WATHEROO': '6513',
+    'GREEN HEAD': '6514',
+    'LEEMAN': '6514',
+    'COOROW': '6515',
+    'EGANU': '6515',
+    'MARCHAGEE': '6515',
+    'WADDY FOREST': '6515',
+    'JURIEN BAY': '6516',
+    'CARNAMAH': '6517',
+    'ENEABBA': '6518',
+    'WARRADARGE': '6518',
+    'ARRINO': '6519',
+    'ARROWSMITH EAST': '6519',
+    'DUDAWA': '6519',
+    'KADATHINNI': '6519',
+    'THREE SPRINGS': '6519',
+    'WOMARDEN': '6519',
+    'BADGINGARRA': '6521',
+    'BOOTHENDARRA': '6521',
+    'GREY': '6521',
+    'NAMBUNG': '6521',
+    'HOLMWOOD': '6522',
+    'IKEWA': '6522',
+    'LOCKIER': '6522',
+    'MINGENEW': '6522',
+    'MOORIARY': '6522',
+    'MOUNT BUDD': '6522',
+    'NANGETTY': '6522',
+    'YANDANOOKA': '6522',
+    'YARRAGADEE': '6522',
+    'ALLANOOKA': '6525',
+    'ARROWSMITH': '6525',
+    'BONNIEFIELD': '6525',
+    'BOOKARA': '6525',
+    'DONGARA': '6525',
+    'IRWIN': '6525',
+    'MILO': '6525',
+    'MOUNT ADAMS': '6525',
+    'MOUNT HORNER': '6525',
+    'PORT DENISON': '6525',
+    'YARDARINO': '6525',
+    'MOUNT HILL': '6528',
+    'SOUTH GREENOUGH': '6528',
+    'WALKAWAY': '6528',
+    'BEACHLANDS': '6530',
+    'BERESFORD': '6530',
+    'BLUFF POINT': '6530',
+    'GERALDTON': '6530',
+    'GERALDTON DC': '6530',
+    'HOUTMAN ABROLHOS ISLANDS': '6530',
+    'KARLOO': '6530',
+    'MAHOMETS FLATS': '6530',
+    'MERU': '6530',
+    'MOUNT TARCOOLA': '6530',
+    'RANGEWAY': '6530',
+    'SUNSET BEACH': '6530',
+    'TARCOOLA BEACH': '6530',
+    'UTAKARRA': '6530',
+    'WAGGRAKINE': '6530',
+    'WANDINA': '6530',
+    'WEBBERTON': '6530',
+    'WONTHELLA': '6530',
+    'WOORREE': '6530',
+    'AJANA': '6532',
+    'BINNU': '6532',
+    'BOOTENAL': '6532',
+    'BRINGO': '6532',
+    'BULLER': '6532',
+    'BURMA ROAD': '6532',
+    'CAPE BURNEY': '6532',
+    'CARRARANG': '6532',
+    'COBURN': '6532',
+    'COOLCALALAYA': '6532',
+    'DEEPDALE': '6532',
+    'DINDILOA': '6532',
+    'DRUMMOND COVE': '6532',
+    'DURAWAH': '6532',
+    'EAST CHAPMAN': '6532',
+    'EAST NABAWA': '6532',
+    'EAST YUNA': '6532',
+    'ELLENDALE': '6532',
+    'ERADU': '6532',
+    'ERADU SOUTH': '6532',
+    'EURARDY': '6532',
+    'GREENOUGH': '6532',
+    'HAMELIN POOL': '6532',
+    'HICKETY': '6532',
+    'HOWATHARRA': '6532',
+    'KOJARENA': '6532',
+    'MARRAH': '6532',
+    'MEADOW': '6532',
+    'MINNENOOKA': '6532',
+    'MOONYOONOOKA': '6532',
+    'MOUNT ERIN': '6532',
+    'NABAWA': '6532',
+    'NANSON': '6532',
+    'NARALING': '6532',
+    'NARNGULU': '6532',
+    'NARRA TARRA': '6532',
+    'NERREN NERREN': '6532',
+    'NOLBA': '6532',
+    'NORTH ERADU': '6532',
+    'NORTHERN GULLY': '6532',
+    'OAKAJEE': '6532',
+    'ROCKWELL': '6532',
+    'RUDDS GULLY': '6532',
+    'SANDSPRINGS': '6532',
+    'SOUTH YUNA': '6532',
+    'TAMALA': '6532',
+    'TIBRADDEN': '6532',
+    'TOOLONGA': '6532',
+    'WEST BINNU': '6532',
+    'WHITE PEAK': '6532',
+    'WICHERINA': '6532',
+    'WICHERINA SOUTH': '6532',
+    'YETNA': '6532',
+    'YUNA': '6532',
+    'BOWES': '6535',
+    'EAST BOWES': '6535',
+    'HORROCKS': '6535',
+    'ISSEKA': '6535',
+    'NORTHAMPTON': '6535',
+    'OGILVIE': '6535',
+    'YALLABATHARRA': '6535',
+    'KALBARRI': '6536',
+    'KALBARRI NATIONAL PARK': '6536',
+    'ZUYTDORP': '6536',
+    'DENHAM': '6537',
+    'DIRK HARTOG ISLAND': '6537',
+    'FRANCOIS PERON NATIONAL PARK': '6537',
+    'MONKEY MIA': '6537',
+    'NANGA': '6537',
+    'SHARK BAY': '6537',
+    'USELESS LOOP': '6537',
+    'BEECHINA': '6556',
+    'CHIDLOW': '6556',
+    'GORRIE': '6556',
+    'MALMALLING': '6556',
+    'THE LAKES': '6556',
+    'WOOROLOO': '6558',
+    'WUNDOWIE': '6560',
+    'BAKERS HILL': '6562',
+    'WOOTTATING': '6562',
+    'CLACKLINE': '6564',
+    'BEJOORDING': '6566',
+    'CARANI': '6566',
+    'COONDLE': '6566',
+    'CULHAM': '6566',
+    'DUMBARTON': '6566',
+    'HODDYS WELL': '6566',
+    'KATRINE': '6566',
+    'NUNILE': '6566',
+    'TOODYAY': '6566',
+    'WEST TOODYAY': '6566',
+    'DEWARS POOL': '6567',
+    'JULIMAR': '6567',
+    'MOONDYNE': '6567',
+    'BOLGART': '6568',
+    'WATTENING': '6568',
+    'WYENING': '6568',
+    'CALINGIRI': '6569',
+    'OLD PLAINS': '6569',
+    'YERECOIN': '6571',
+    'PIAWANING': '6572',
+    'BINDI BINDI': '6574',
+    'GABALONG': '6574',
+    'MILING': '6575',
+    'KONNONGORRING': '6603',
+    'LAKE HINDS': '6603',
+    'LAKE NINAN': '6603',
+    'MOCARDY': '6603',
+    'WONGAN HILLS': '6603',
+    'KONDUT': '6605',
+    'BALLIDU': '6606',
+    'EAST BALLIDU': '6606',
+    'WEST BALLIDU': '6606',
+    'EAST DAMBORING': '6608',
+    'MARNE': '6608',
+    'PITHARA': '6608',
+    'DALWALLINU': '6609',
+    'NUGADONG': '6609',
+    'XANTIPPE': '6609',
+    'JIBBERDING': '6612',
+    'MIAMOON': '6612',
+    'PAYNES FIND': '6612',
+    'WUBIN': '6612',
+    'BUNTINE': '6613',
+    'MAYA': '6614',
+    'PERENJORI': '6620',
+    'ROTHSAY': '6620',
+    'BOWGADA': '6623',
+    'BUNJIL': '6623',
+    'GUTHA': '6623',
+    'KOOLANOOKA': '6623',
+    'MORAWA': '6623',
+    'PINTHARUKA': '6623',
+    'MERKANOOKA': '6625',
+    'CANNA': '6627',
+    'TARDUN': '6628',
+    'DEVILS CREEK': '6630',
+    'MULLEWA': '6630',
+    'NERRAMYNE': '6630',
+    'NUNIERRA': '6630',
+    'WEST CASUARINAS': '6630',
+    'WONGOONDY': '6630',
+    'WOOLGORONG': '6630',
+    'PINDAR': '6631',
+    'AMBANIA': '6632',
+    'TENINDEWA': '6632',
+    'SOUTH MURCHISON': '6635',
+    'YALGOO': '6635',
+    'COOLADAR HILL': '6638',
+    'DAGGAR HILLS': '6638',
+    'MOUNT MAGNET': '6638',
+    'SANDSTONE': '6639',
+    'CUE': '6640',
+    'EAST MURCHISON': '6640',
+    'LAKE AUSTIN': '6640',
+    'REEDY': '6640',
+    'WELD RANGE': '6640',
+    'ANGELO RIVER': '6642',
+    'CAPRICORN': '6642',
+    'KUMARINA': '6642',
+    'MEEKATHARRA': '6642',
+    'LAKE CARNEGIE': '6646',
+    'LITTLE SANDY DESERT': '6646',
+    'WILUNA': '6646',
+    'BABBAGE ISLAND': '6701',
+    'BERNIER ISLAND': '6701',
+    'BROCKMAN': '6701',
+    'BROWN RANGE': '6701',
+    'CARBLA': '6701',
+    'CARNARVON': '6701',
+    'CORAL BAY': '6701',
+    'DORRE ISLAND': '6701',
+    'EAST CARNARVON': '6701',
+    'GILROYD': '6701',
+    'GREYS PLAIN': '6701',
+    'INGGARDA': '6701',
+    'KENNEDY RANGE': '6701',
+    'LYNDON': '6701',
+    'MASSEY BAY': '6701',
+    'MINILYA': '6701',
+    'MORGANTOWN': '6701',
+    'NINGALOO': '6701',
+    'NORTH PLANTATIONS': '6701',
+    'SOUTH CARNARVON': '6701',
+    'SOUTH PLANTATIONS': '6701',
+    'TALISKER': '6701',
+    'WOORAMEL': '6701',
+    'YALARDY': '6701',
+    'YANDOO CREEK': '6701',
+    'EAST LYONS RIVER': '6705',
+    'GASCOYNE JUNCTION': '6705',
+    'GASCOYNE RIVER': '6705',
+    'WEST LYONS RIVER': '6705',
+    'CAPE RANGE NATIONAL PARK': '6707',
+    'EXMOUTH': '6707',
+    'EXMOUTH GULF': '6707',
+    'NORTH WEST CAPE': '6707',
+    'CANE': '6710',
+    'ONSLOW': '6710',
+    'PEEDAMULLA': '6710',
+    'TALANDJI': '6710',
+    'YANNARIE': '6710',
+    'THEVENARD ISLAND': '6711',
+    'BARROW ISLAND': '6712',
+    'DAMPIER': '6713',
+    'DAMPIER ARCHIPELAGO': '6713',
+    'ANTONYMYRE': '6714',
+    'BALLA BALLA': '6714',
+    'BULGARRA': '6714',
+    'BURRUP': '6714',
+    'CLEAVERVILLE': '6714',
+    'COOYA POOYA': '6714',
+    'GAP RIDGE': '6714',
+    'GNOOREA': '6714',
+    'KARRATHA': '6714',
+    'KARRATHA INDUSTRIAL ESTATE': '6714',
+    'MARDIE': '6714',
+    'MILLARS WELL': '6714',
+    'MOUNT ANKETELL': '6714',
+    'MULATAGA': '6714',
+    'NICKOL': '6714',
+    'PEGS CREEK': '6714',
+    'STOVE HILL': '6714',
+    'FORTESCUE': '6716',
+    'HAMERSLEY RANGE': '6716',
+    'PANNAWONICA': '6716',
+    'ROEBOURNE': '6718',
+    'WHIM CREEK': '6718',
+    'POINT SAMSON': '6720',
+    'INDEE': '6721',
+    'MUNDABULLANGANA': '6721',
+    'PARDOO': '6721',
+    'PORT HEDLAND': '6721',
+    'STRELLEY': '6721',
+    'WALLAREENYA': '6721',
+    'WEDGEFIELD': '6721',
+    'BOODARIE': '6722',
+    'DE GREY': '6722',
+    'FINUCANE': '6722',
+    'PIPPINGARRA': '6722',
+    'SOUTH HEDLAND': '6722',
+    'BILINGURR': '6725',
+    'BROOME': '6725',
+    'DAMPIER PENINSULA': '6725',
+    'DJUGUN': '6725',
+    'EIGHTY MILE BEACH': '6725',
+    'GINGERAH': '6725',
+    'LAGRANGE': '6725',
+    'MINYIRR': '6725',
+    'ROEBUCK': '6725',
+    'WATERBANK': '6725',
+    'CABLE BEACH': '6726',
+    'CAMBALLIN': '6728',
+    'GEEGULLY CREEK': '6728',
+    'KING LEOPOLD RANGES': '6728',
+    'MEDA': '6728',
+    'ST GEORGE RANGES': '6728',
+    'WILLARE': '6728',
+    'COCKATOO ISLAND': '6731',
+    'KOOLAN ISLAND': '6733',
+    'DRYSDALE RIVER': '6740',
+    'KALUMBURU': '6740',
+    'MITCHELL PLATEAU': '6740',
+    'OOMBULGURRI': '6740',
+    'PRINCE REGENT RIVER': '6740',
+    'CAMBRIDGE GULF': '6743',
+    'GIBB': '6743',
+    'KUNUNURRA': '6743',
+    'LAKE ARGYLE': '6743',
+    'WARMUN': '6743',
+    'INNAWANGA': '6751',
+    'JUNA DOWNS': '6751',
+    'KARIJINI': '6751',
+    'MOUNT SHEILA': '6751',
+    'MULGA DOWNS': '6751',
+    'NANUTARRA': '6751',
+    'TOM PRICE': '6751',
+    'WITTENOOM': '6751',
+    'NEWMAN': '6753',
+    'PARABURDOO': '6754',
+    'NULLAGINE': '6758',
+    'MARBLE BAR': '6760',
+    'TELFER': '6762',
+    'FITZROY CROSSING': '6765',
+    'MOUNT HARDMAN': '6765',
+    'MCBEATH': '6770',
+    'MUELLER RANGES': '6770',
+    'ORD RIVER': '6770',
+    'PURNULULU': '6770',
+    'STURT CREEK': '6770',
+    'CHRISTMAS ISLAND': '6798',
+    'HOME ISLAND COCOS (KEELING) ISLANDS': '6799',
+    'WEST ISLAND COCOS (KEELING) ISLANDS': '6799',
+    'BATHURST STREET PO': '7000',
+    'HOBART': '7000',
+    'NORTH HOBART': '7000',
+    'QUEENS DOMAIN': '7000',
+    'WEST HOBART': '7000',
+    'TASMAN ISLAND': '7001',
+    'BATTERY POINT': '7004',
+    'SOUTH HOBART': '7004',
+    'DYNNYRNE': '7005',
+    'LOWER SANDY BAY': '7005',
+    'SANDY BAY': '7005',
+    'UNIVERSITY OF TASMANIA': '7005',
+    'MOUNT NELSON': '7007',
+    'TOLMANS HILL': '7007',
+    'LENAH VALLEY': '7008',
+    'DERWENT PARK': '7009',
+    'LUTANA': '7009',
+    'MOONAH': '7009',
+    'WEST MOONAH': '7009',
+    'DOWSING POINT': '7010',
+    'ROSETTA': '7010',
+    'AUSTINS FERRY': '7011',
+    'BERRIEDALE': '7011',
+    'CHIGWELL': '7011',
+    'COLLINSVALE': '7012',
+    'GLENLUSK': '7012',
+    'GEILSTON BAY': '7015',
+    'LINDISFARNE': '7015',
+    'RISDON VALE': '7016',
+    'GRASSTREE HILL': '7017',
+    'HONEYWOOD': '7017',
+    'OLD BEACH': '7017',
+    'OTAGO': '7017',
+    'RISDON': '7017',
+    'TEA TREE': '7017',
+    'BELLERIVE': '7018',
+    'HOWRAH': '7018',
+    'MONTAGU BAY': '7018',
+    'ROSNY': '7018',
+    'ROSNY PARK': '7018',
+    'WARRANE': '7018',
+    'CLARENDON VALE': '7019',
+    'OAKDOWNS': '7019',
+    'LAUDERDALE': '7021',
+    'OPOSSUM BAY': '7023',
+    'DULCOT': '7025',
+    'CAMPANIA': '7026',
+    'COLEBROOK': '7027',
+    'ARTHURS LAKE': '7030',
+    'BAGDAD': '7030',
+    'BAGDAD NORTH': '7030',
+    'BOTHWELL': '7030',
+    'BROADMARSH': '7030',
+    'CRAMPS BAY': '7030',
+    'FLINTSTONE': '7030',
+    'GAGEBROOK': '7030',
+    'GRANTON': '7030',
+    'HERMITAGE': '7030',
+    'INTERLAKEN': '7030',
+    'LAKE SORELL': '7030',
+    'LIAWENEE': '7030',
+    'LOWER MARSHES': '7030',
+    'MELTON MOWBRAY': '7030',
+    'MIENA': '7030',
+    'MILLERS BLUFF': '7030',
+    'MORASS BAY': '7030',
+    'PONTVILLE': '7030',
+    'STEPPES': '7030',
+    'TODS CORNER': '7030',
+    'WADDAMANA': '7030',
+    'WILBURVILLE': '7030',
+    'ALBION HEIGHTS': '7050',
+    'KINGSTON BEACH': '7050',
+    'BLACKMANS BAY': '7052',
+    'BONNET HILL': '7053',
+    'TAROONA': '7053',
+    'BARRETTA': '7054',
+    'CONINGHAM': '7054',
+    'ELECTRONA': '7054',
+    'FERN TREE': '7054',
+    'HOWDEN': '7054',
+    'LESLIE VALE': '7054',
+    'LOWER SNUG': '7054',
+    'NEIKA': '7054',
+    'RIDGEWAY': '7054',
+    'SNUG': '7054',
+    'TINDERBOX': '7054',
+    'WELLINGTON PARK': '7054',
+    'HUNTINGFIELD': '7055',
+    'CRABTREE': '7109',
+    'CRADOC': '7109',
+    'GLAZIERS BAY': '7109',
+    'GLEN HUON': '7109',
+    'GLENDEVIE': '7109',
+    'GROVE': '7109',
+    'HUONVILLE': '7109',
+    'IDA BAY': '7109',
+    'JUDBURY': '7109',
+    'LONNAVALE': '7109',
+    'LOWER LONGLEY': '7109',
+    'LOWER WATTLE GROVE': '7109',
+    'LUCASTON': '7109',
+    'LUNE RIVER': '7109',
+    'LYMINGTON': '7109',
+    'MOUNTAIN RIVER': '7109',
+    'PETCHEYS BAY': '7109',
+    'RAMINEA': '7109',
+    'RANELAGH': '7109',
+    'RECHERCHE': '7109',
+    'SOUTHPORT LAGOON': '7109',
+    'STRATHBLANE': '7109',
+    'ABELS BAY': '7112',
+    'CHARLOTTE COVE': '7112',
+    'CYGNET': '7112',
+    'DEEP BAY': '7112',
+    'EGGS AND BACON BAY': '7112',
+    'GARDEN ISLAND CREEK': '7112',
+    'GARDNERS BAY': '7112',
+    'NICHOLLS RIVULET': '7112',
+    'RANDALLS BAY': '7112',
+    'VERONA SANDS': '7112',
+    'BROOKS BAY': '7116',
+    'CAIRNS BAY': '7116',
+    'CASTLE FORBES BAY': '7116',
+    'GEEVESTON': '7116',
+    'POLICE POINT': '7116',
+    'PORT HUON': '7116',
+    'SURGES BAY': '7116',
+    'SURVEYORS BAY': '7116',
+    'DOVER': '7117',
+    'STONOR': '7119',
+    'ANDOVER': '7120',
+    'ANTILL PONDS': '7120',
+    'BADEN': '7120',
+    'LEMONT': '7120',
+    'LEVENDALE': '7120',
+    'MOUNT SEYMOUR': '7120',
+    'PARATTAH': '7120',
+    'PAWTELLA': '7120',
+    'RHYNDASTON': '7120',
+    'SWANSTON': '7120',
+    'TIBERIAS': '7120',
+    'TUNBRIDGE': '7120',
+    'TUNNACK': '7120',
+    'WHITEFOORD': '7120',
+    'WOODSDALE': '7120',
+    'YORK PLAINS': '7120',
+    'STRATHGORDON': '7139',
+    'BLACK HILLS': '7140',
+    'BOYER': '7140',
+    'BRADYS LAKE': '7140',
+    'BRONTE PARK': '7140',
+    'BUTLERS GORGE': '7140',
+    'DEE': '7140',
+    'DERWENT BRIDGE': '7140',
+    'FENTONBURY': '7140',
+    'FLORENTINE': '7140',
+    'GLENORA': '7140',
+    'GRETNA': '7140',
+    'HAYES': '7140',
+    'HOLLOW TREE': '7140',
+    'KARANJA': '7140',
+    'LACHLAN': '7140',
+    'LAKE ST CLAIR': '7140',
+    'LAWITTA': '7140',
+    'LITTLE PINE LAGOON': '7140',
+    'LONDON LAKES': '7140',
+    'MACQUARIE PLAINS': '7140',
+    'MAGRA': '7140',
+    'MALBINA': '7140',
+    'MAYDENA': '7140',
+    'MOOGARA': '7140',
+    'MOUNT FIELD': '7140',
+    'MOUNT LLOYD': '7140',
+    'NATIONAL PARK': '7140',
+    'NEW NORFOLK': '7140',
+    'OUSE': '7140',
+    'ROSEGARLAND': '7140',
+    'SORELL CREEK': '7140',
+    'STRICKLAND': '7140',
+    'STYX': '7140',
+    'TARRALEAH': '7140',
+    'UXBRIDGE': '7140',
+    'WAYATINAH': '7140',
+    'WESTERWAY': '7140',
+    'ADVENTURE BAY': '7150',
+    'ALLENS RIVULET': '7150',
+    'ALONNAH': '7150',
+    'BARNES BAY': '7150',
+    'DENNES POINT': '7150',
+    'GREAT BAY': '7150',
+    'KAOOTA': '7150',
+    'KILLORA': '7150',
+    'LONGLEY': '7150',
+    'LUNAWANNA': '7150',
+    'NORTH BRUNY': '7150',
+    'PELVERATA': '7150',
+    'SANDFLY': '7150',
+    'SIMPSONS BAY': '7150',
+    'SOUTH BRUNY': '7150',
+    'UPPER WOODSTOCK': '7150',
+    'CASEY': '7151',
+    'DAVIS': '7151',
+    'MACQUARIE ISLAND': '7151',
+    'KETTERING': '7155',
+    'BIRCHS BAY': '7162',
+    'FLOWERPOT': '7163',
+    'MOUNT RUMNEY': '7170',
+    'ROCHES BEACH': '7170',
+    'SEVEN MILE BEACH': '7170',
+    'MIDWAY POINT': '7171',
+    'PENNA': '7171',
+    'NUGENT': '7172',
+    'ORIELTON': '7172',
+    'PAWLEENA': '7172',
+    'SORELL': '7172',
+    'CARLTON RIVER': '7173',
+    'CONNELLYS MARSH': '7173',
+    'DODGES FERRY': '7173',
+    'FORCETT': '7173',
+    'PRIMROSE SANDS': '7173',
+    'COPPING': '7174',
+    'BREAM CREEK': '7175',
+    'KELLEVIE': '7176',
+    'BOOMER BAY': '7177',
+    'DUNALLEY': '7177',
+    'MURDUNNA': '7178',
+    'EAGLEHAWK NECK': '7179',
+    'TARANNA': '7180',
+    'HIGHCROFT': '7183',
+    'NUBEENA': '7184',
+    'STORMLEA': '7184',
+    'WHITE BEACH': '7184',
+    'PREMAYDENA': '7185',
+    'SALTWATER RIVER': '7186',
+    'SLOPING MAIN': '7186',
+    'KOONYA': '7187',
+    'APSLAWN': '7190',
+    'DOLPHIN SANDS': '7190',
+    'LITTLE SWANPORT': '7190',
+    'PONTYPOOL': '7190',
+    'RHEBAN': '7190',
+    'ROCKY HILLS': '7190',
+    'SPRING BEACH': '7190',
+    'TRIABUNNA': '7190',
+    'TOOMS LAKE': '7209',
+    'CAMPBELL TOWN': '7210',
+    'LAKE LEAKE': '7210',
+    'CONARA': '7211',
+    'EPPING FOREST': '7211',
+    'BLESSINGTON': '7212',
+    'BURNS CREEK': '7212',
+    'DEDDINGTON': '7212',
+    'NILE': '7212',
+    'UPPER BLESSINGTON': '7212',
+    'WESTERN JUNCTION': '7212',
+    'ROSSARDEN': '7213',
+    'ROYAL GEORGE': '7213',
+    'MANGANA': '7214',
+    'MATHINNA': '7214',
+    'UPPER ESK': '7214',
+    'BICHENO': '7215',
+    'CHAIN OF LAGOONS': '7215',
+    'COLES BAY': '7215',
+    'DOUGLAS RIVER': '7215',
+    'FALMOUTH': '7215',
+    'FREYCINET': '7215',
+    'FRIENDLY BEACHES': '7215',
+    'SCAMANDER': '7215',
+    'UPPER SCAMANDER': '7215',
+    'AKAROA': '7216',
+    'ANSONS BAY': '7216',
+    'BINALONG BAY': '7216',
+    'GOSHEN': '7216',
+    'GOULDS COUNTRY': '7216',
+    'LOTTAH': '7216',
+    'PYENGANA': '7216',
+    'STIEGLITZ': '7216',
+    'NEWNHAM': '7248',
+    'ROCHERLEA': '7248',
+    'KINGS MEADOWS': '7249',
+    'SOUTH LAUNCESTON': '7249',
+    'YOUNGTOWN': '7249',
+    'BLACKSTONE HEIGHTS': '7250',
+    'EAST LAUNCESTON': '7250',
+    'LAUNCESTON': '7250',
+    'NORWOOD AVENUE PO': '7250',
+    'PROSPECT VALE': '7250',
+    'SUMMERHILL': '7250',
+    'TRAVELLERS REST': '7250',
+    'TREVALLYN': '7250',
+    'WEST LAUNCESTON': '7250',
+    'BEECHFORD': '7252',
+    'DILSTON': '7252',
+    'HILLWOOD': '7252',
+    'LEFROY': '7252',
+    'LULWORTH': '7252',
+    'MOUNT DIRECTION': '7252',
+    'PIPERS RIVER': '7252',
+    'STONY HEAD': '7252',
+    'WEYMOUTH': '7252',
+    'BELL BAY': '7253',
+    'GEORGE TOWN': '7253',
+    'LONG REACH': '7253',
+    'LOW HEAD': '7253',
+    'BELLINGHAM': '7254',
+    'GOLCONDA': '7254',
+    'LEBRINA': '7254',
+    'PIPERS BROOK': '7254',
+    'TUNNEL': '7254',
+    'WYENA': '7254',
+    'BLUE ROCKS': '7255',
+    'EMITA': '7255',
+    'KILLIECRANKIE': '7255',
+    'LACKRANA': '7255',
+    'LADY BARRON': '7255',
+    'LEEKA': '7255',
+    'LOCCOTA': '7255',
+    'LUGHRATA': '7255',
+    'MEMANA': '7255',
+    'PALANA': '7255',
+    'RANGA': '7255',
+    'WHITEMARK': '7255',
+    'WINGAROO': '7255',
+    'CURRIE': '7256',
+    'EGG LAGOON': '7256',
+    'GRASSY': '7256',
+    'LOORANA': '7256',
+    'LYMWOOD': '7256',
+    'NARACOOPA': '7256',
+    'NUGARA': '7256',
+    'PEARSHAPE': '7256',
+    'PEGARAH': '7256',
+    'REEKARA': '7256',
+    'SEA ELEPHANT': '7256',
+    'SURPRISE BAY': '7256',
+    'YAMBACOONA': '7256',
+    'YARRA CREEK': '7256',
+    'CAPE BARREN ISLAND': '7257',
+    'RELBIA': '7258',
+    'NUNAMARA': '7259',
+    'PATERSONIA': '7259',
+    'TARGA': '7259',
+    'TAYENE': '7259',
+    'BLUMONT': '7260',
+    'CUCKOO': '7260',
+    'FORESTER': '7260',
+    'JETSONVILLE': '7260',
+    'KAMONA': '7260',
+    'LIETINNA': '7260',
+    'LISLE': '7260',
+    'NABOWLA': '7260',
+    'NORTH SCOTTSDALE': '7260',
+    'SCOTTSDALE': '7260',
+    'SOUTH SPRINGFIELD': '7260',
+    'TONGANAH': '7260',
+    'TULENDEENA': '7260',
+    'WEST SCOTTSDALE': '7260',
+    'BRANXHOLM': '7261',
+    'WARRENTINNA': '7261',
+    'BRIDPORT': '7262',
+    'TOMAHAWK': '7262',
+    'WATERHOUSE': '7262',
+    'LEGERWOOD': '7263',
+    'RINGAROOMA': '7263',
+    'TALAWA': '7263',
+    'TRENAH': '7263',
+    'BOOBYALLA': '7264',
+    'CAPE PORTLAND': '7264',
+    'EDDYSTONE': '7264',
+    'EDDYSTONE POINT': '7264',
+    'HERRICK': '7264',
+    'MUSSELROE BAY': '7264',
+    'RUSHY LAGOON': '7264',
+    'SOUTH MOUNT CAMERON': '7264',
+    'TELITA': '7264',
+    'WELDBOROUGH': '7264',
+    'BANCA': '7265',
+    'WINNALEAH': '7265',
+    'KAROOLA': '7267',
+    'LALLA': '7267',
+    'LOWER TURNERS MARSH': '7267',
+    'TURNERS MARSH': '7267',
+    'NORTH LILYDALE': '7268',
+    'BADGER HEAD': '7270',
+    'BEAUTY POINT': '7270',
+    'CLARENCE POINT': '7270',
+    'FLOWERY GULLY': '7270',
+    'GREENS BEACH': '7270',
+    'KAYENA': '7270',
+    'ROWELLA': '7270',
+    'SIDMOUTH': '7270',
+    'YORK TOWN': '7270',
+    'DEVIOT': '7275',
+    'FRANKFORD': '7275',
+    'HOLWELL': '7275',
+    'LANENA': '7275',
+    'LOIRA': '7275',
+    'NOTLEY HILLS': '7275',
+    'ROBIGANA': '7275',
+    'SWAN POINT': '7275',
+    'WINKLEIGH': '7275',
+    'GRAVELLY BEACH': '7276',
+    'BRIDGENORTH': '7277',
+    'GRINDELWALD': '7277',
+    'LEGANA': '7277',
+    'ROSEVEARS': '7277',
+    'HADSPEN': '7290',
+    'HAGLEY': '7292',
+    'QUAMBY BEND': '7292',
+    'SELBOURNE': '7292',
+    'DEVON HILLS': '7300',
+    'POWRANNA': '7300',
+    'BISHOPSBOURNE': '7301',
+    'BLACKWOOD CREEK': '7301',
+    'LIFFEY': '7301',
+    'TOIBERRY': '7301',
+    'BRACKNELL': '7302',
+    'POATINA': '7302',
+    'BIRRALEE': '7303',
+    'CLUAN': '7303',
+    'EXTON': '7303',
+    'OAKS': '7303',
+    'OSMASTON': '7303',
+    'WHITEMORE': '7303',
+    'BRANDUM': '7304',
+    'BREONA': '7304',
+    'CAVESIDE': '7304',
+    'CENTRAL PLATEAU': '7304',
+    'CHUDLEIGH': '7304',
+    'DAIRY PLAINS': '7304',
+    'DELORAINE': '7304',
+    'DOCTORS POINT': '7304',
+    'DUNORLAN': '7304',
+    'ELIZABETH TOWN': '7304',
+    'GOLDEN VALLEY': '7304',
+    'JACKEYS MARSH': '7304',
+    'LIENA': '7304',
+    'MAYBERRY': '7304',
+    'MEANDER': '7304',
+    'MERSEY FOREST': '7304',
+    'MOLE CREEK': '7304',
+    'MOLTEMA': '7304',
+    'MONTANA': '7304',
+    'NEEDLES': '7304',
+    'PARKHAM': '7304',
+    'QUAMBY BROOK': '7304',
+    'RED HILLS': '7304',
+    'REEDY MARSH': '7304',
+    'REYNOLDS NECK': '7304',
+    'WEEGENA': '7304',
+    'WEETAH': '7304',
+    'MERSEYLEA': '7305',
+    'RAILTON': '7305',
+    'ACACIA HILLS': '7306',
+    'CETHANA': '7306',
+    'CLAUDE ROAD': '7306',
+    'CRADLE MOUNTAIN': '7306',
+    'GOWRIE PARK': '7306',
+    'LORINNA': '7306',
+    'LOWER BARRINGTON': '7306',
+    'LOWER BEULAH': '7306',
+    'NOOK': '7306',
+    'NOWHERE ELSE': '7306',
+    'ROLAND': '7306',
+    'SHEFFIELD': '7306',
+    'STAVERTON': '7306',
+    'STOODLEY': '7306',
+    'WEST KENTISH': '7306',
+    'BAKERS BEACH': '7307',
+    'HARFORD': '7307',
+    'HAWLEY BEACH': '7307',
+    'LATROBE': '7307',
+    'MORIARTY': '7307',
+    'NORTHDOWN': '7307',
+    'PORT SORELL': '7307',
+    'SHEARWATER': '7307',
+    'SQUEAKING POINT': '7307',
+    'THIRLSTANE': '7307',
+    'WESLEY VALE': '7307',
+    'AMBLESIDE': '7310',
+    'DEVONPORT': '7310',
+    'DON': '7310',
+    'EAST DEVONPORT': '7310',
+    'ERRIBA': '7310',
+    'EUGENANA': '7310',
+    'FORTH': '7310',
+    'FORTHSIDE': '7310',
+    'KINDRED': '7310',
+    'LOWER WILMOT': '7310',
+    'MOINA': '7310',
+    'PALOONA': '7310',
+    'QUOIBA': '7310',
+    'SOUTH SPREYTON': '7310',
+    'SPREYTON': '7310',
+    'STONY RISE': '7310',
+    'TARLETON': '7310',
+    'TUGRAH': '7310',
+    'WEST DEVONPORT': '7310',
+    'WILMOT': '7310',
+    'ABBOTSHAM': '7315',
+    'CASTRA': '7315',
+    'GUNNS PLAINS': '7315',
+    'LEITH': '7315',
+    'LOONGANA': '7315',
+    'NIETTA': '7315',
+    'NORTH MOTTON': '7315',
+    'SOUTH NIETTA': '7315',
+    'SOUTH PRESTON': '7315',
+    'SPALFORD': '7315',
+    'SPRENT': '7315',
+    'TURNERS BEACH': '7315',
+    'ULVERSTONE': '7315',
+    'UPPER CASTRA': '7315',
+    'WEST ULVERSTONE': '7315',
+    'CAMENA': '7316',
+    'CUPRONA': '7316',
+    'HEYBRIDGE': '7316',
+    'HOWTH': '7316',
+    'LOYETEA': '7316',
+    'PENGUIN': '7316',
+    'PRESERVATION BAY': '7316',
+    'RIANA': '7316',
+    'SOUTH RIANA': '7316',
+    'SULPHUR CREEK': '7316',
+    'WEST PINE': '7316',
+    'BURNIE': '7320',
+    'CAMDALE': '7320',
+    'COOEE': '7320',
+    'DOWNLANDS': '7320',
+    'HAVENVIEW': '7320',
+    'MONTELLO': '7320',
+    'OCEAN VISTA': '7320',
+    'PARK GROVE': '7320',
+    'ROMAINE': '7320',
+    'SHOREWELL PARK': '7320',
+    'SOUTH BURNIE': '7320',
+    'UPPER BURNIE': '7320',
+    'WIVENHOE': '7320',
+    'BOAT HARBOUR BEACH': '7321',
+    'CHASM CREEK': '7321',
+    'CORINNA': '7321',
+    'COWRIE POINT': '7321',
+    'CRAYFISH CREEK': '7321',
+    'DETENTION': '7321',
+    'EAST CAM': '7321',
+    'EAST RIDGLEY': '7321',
+    'EDGCUMBE BEACH': '7321',
+    'HAMPSHIRE': '7321',
+    'HELLYER': '7321',
+    'HIGHCLERE': '7321',
+    'LUINA': '7321',
+    'MAWBANNA': '7321',
+    'MONTUMANA': '7321',
+    'MOOREVILLE': '7321',
+    'NATONE': '7321',
+    'PARRAWE': '7321',
+    'PORT LATTA': '7321',
+    'RIDGLEY': '7321',
+    'ROCKY CAPE': '7321',
+    'SAVAGE RIVER': '7321',
+    'SISTERS BEACH': '7321',
+    'STOWPORT': '7321',
+    'TEWKESBURY': '7321',
+    'TULLAH': '7321',
+    'UPPER NATONE': '7321',
+    'UPPER STOWPORT': '7321',
+    'WEST MOOREVILLE': '7321',
+    'WEST RIDGLEY': '7321',
+    'WILTSHIRE': '7321',
+    'SOMERSET': '7322',
+    'CALDER': '7325',
+    'DOCTORS ROCKS': '7325',
+    'HENRIETTA': '7325',
+    'LAPOINYA': '7325',
+    'MEUNNA': '7325',
+    'MILABENA': '7325',
+    'MOORLEAH': '7325',
+    'MOUNT HICKS': '7325',
+    'OLDINA': '7325',
+    'OONAH': '7325',
+    'PREOLENNA': '7325',
+    'SISTERS CREEK': '7325',
+    'TABLE CAPE': '7325',
+    'TAKONE': '7325',
+    'WEST TAKONE': '7325',
+    'WYNYARD': '7325',
+    'YOLLA': '7325',
+    'ALCOMIE': '7330',
+    'BRITTONS SWAMP': '7330',
+    'COUTA ROCKS': '7330',
+    'EDITH CREEK': '7330',
+    'FOREST': '7330',
+    'LILEAH': '7330',
+    'MARRAWAH': '7330',
+    'MELLA': '7330',
+    'MENGHA': '7330',
+    'MONTAGU': '7330',
+    'NABAGEENA': '7330',
+    'REDPA': '7330',
+    'ROGER RIVER': '7330',
+    'SCOPUS': '7330',
+    'SCOTCHTOWN': '7330',
+    'SMITHTON': '7330',
+    'SOUTH FOREST': '7330',
+    'TEMMA': '7330',
+    'THREE HUMMOCK ISLAND': '7330',
+    'TOGARI': '7330',
+    'TROWUTTA': '7330',
+    'WEST MONTAGU': '7330',
+    'WOOLNORTH': '7330',
+    'GORMANSTON': '7466',
+    'LAKE MARGARET': '7467',
+    'MACQUARIE HEADS': '7468',
+    'STRAHAN': '7468',
+    'GRANVILLE HARBOUR': '7469',
+    'RENISON BELL': '7469',
+    'TRIAL HARBOUR': '7469',
+    'ZEEHAN': '7469',
+}
diff --git a/labsurv/liccheck.py b/labsurv/liccheck.py
new file mode 100644
index 0000000..ae1aae2
--- /dev/null
+++ b/labsurv/liccheck.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# This tool checks that all .py and .html files contain the following license
+# header text:
+
+"""
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+"""
+
+import sys
+import os
+import re
+
+# Check all files with these extensions
+check_exts = '.py', '.html', '.js', '.css'
+# Additional files to check
+extras = [
+]
+# Ignore listed files
+ignore = [
+    'labsurv/pcode.py',
+]
+
+ignore_dirs = [
+    'simpleinst',                       # External pkg, HACOS Licence
+]
+
+filt_re = re.compile(r'(^(%|#|--|[ \t]+\*)?[ \t]*)|([ \t\r;]+$)', re.MULTILINE)
+
+exit_status = 0
+
+def strip(buf):
+    return filt_re.sub('', buf)
+
+def check(filepath, want):
+    f = open(filepath)
+    try:
+        head = f.read(2048)
+    finally:
+        f.close()
+    if want not in strip(head):
+        global exit_status
+        exit_status = 1
+        print filepath
+
+
+want = strip(__doc__)
+for filepath in extras:
+    check(filepath, want)
+for dirpath, dirnames, filenames in os.walk('.'):
+    dirnames[:] = [dirname 
+                   for dirname in dirnames 
+                   if dirname not in ignore_dirs]
+    for filename in filenames:
+        for ext in check_exts:
+            if filename.endswith(ext):
+                break
+        else:
+            continue
+        filepath = os.path.normpath(os.path.join(dirpath, filename))
+        if filepath in ignore:
+            continue
+        check(filepath, want)
+sys.exit(exit_status)
diff --git a/labsurv/pages/cases.html b/labsurv/pages/cases.html
new file mode 100644
index 0000000..80d7782
--- /dev/null
+++ b/labsurv/pages/cases.html
@@ -0,0 +1,74 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page">
+   <al-setarg name="rtitle">
+    <al-value expr="report.case_page_info()" />
+   </al-setarg>
+   <al-setarg name="title">
+    Step 4: Influenza positive cases this week
+   </al-setarg>
+  <table class="cases">
+   <thead style="font-size: 83%;">
+    <tr>
+     <th></th>
+     <th>Test method</th>
+     <th>Flu</th>
+     <th>Age of case</th>
+     <th>Sex</th>
+     <th>Suburb</th>
+     <th>Postcode</th>
+    </tr>
+   </thead>
+   <tbody>
+    <al-for iter="c_i" expr="report.positive_case_page()">
+     <al-exec expr="case = c_i.value()" />
+     <tr>
+      <th><al-value expr="case.idx + 1" /></th>
+      <td><al-select optionexpr="case.tests" onfocus="from_prev(this);"
+       nameexpr="'report.positive_cases[%d].test' % case.idx" /></td>
+      <td><al-select optionexpr="case.diagnoses" 
+       nameexpr="'report.positive_cases[%d].diagnosis' % case.idx" /></td>
+      <td><al-input size="12"
+       nameexpr="'report.positive_cases[%d].age' % case.idx" /></td>
+      <td><al-select optionexpr="case.sexes"
+       nameexpr="'report.positive_cases[%d].sex' % case.idx" /></td>
+      <td><al-input onblur="this.value = this.value.toUpperCase();"
+       nameexpr="'report.positive_cases[%d].suburb' % case.idx" /></td>
+      <td><al-input onfocus="lookup_postcode(this);"
+       nameexpr="'report.positive_cases[%d].postcode' % case.idx" /></td>
+     </tr>
+    </al-for>
+    <tr>
+     <th align="right">Notes:</th>
+     <td colspan="6">
+      <al-textarea name="report.notes" style="width: 100%;" rows="10" />
+     </td>
+    </tr>
+   </tbody>
+  </table>
+  <al-setarg name="buttons">
+   <table class="buttons">
+    <tr>
+     <td align="left"><al-input type="submit" name="back" value="<< Back" /></td>
+     <td align="right"><al-input type="submit" name="next" value="Next >>" /></td>
+    </tr>
+   </table>
+  </al-setarg>
+</al-expand>
diff --git a/labsurv/pages/date.html b/labsurv/pages/date.html
new file mode 100644
index 0000000..0146b96
--- /dev/null
+++ b/labsurv/pages/date.html
@@ -0,0 +1,60 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page">
+   <al-setarg name="title">
+    Step 1: Select week
+   </al-setarg>
+   <div class="export">
+    <h2>Download</h2>
+    <al-input type="submit" name="export:totals" value="Totals" /><br>
+    <al-input type="submit" name="export:diags" value="Diagnoses" /><br>
+    <al-input type="submit" name="export:cases" value="Cases" /><br>
+    <al-input type="submit" name="export:notes" value="Notes" /><br>
+   </div>
+   <h2>Complete <al-value expr="apptitle"> for:</h2>
+   <table>
+    <tr>
+     <td>
+      <label for="lab">Lab</label>
+     </td>
+     <td>
+      <al-select name="report.lab" id="lab" disabledbool="report.lab_readonly"
+       optionexpr="report.lab_options" />
+     </td>
+    </tr>
+    <tr>
+     <td>
+      <label for="week">for week ending Friday</label>
+     </td>
+     <td>
+      <al-select name="report.week" id="week"
+       style="min-width: 100%;"
+       optionexpr="report.week_options" />
+     </td>
+    </tr>
+   </table>
+   <al-setarg name="buttons">
+    <table class="buttons">
+     <tr>
+      <td align="right"><al-input type="submit" name="next" value="Next >>" /></td>
+     </tr>
+    </table>
+   </al-setarg>
+</al-expand>
diff --git a/labsurv/pages/details.html b/labsurv/pages/details.html
new file mode 100644
index 0000000..7c4b1e5
--- /dev/null
+++ b/labsurv/pages/details.html
@@ -0,0 +1,53 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page">
+   <al-setarg name="title">
+    Step 3: Number of diagnoses made
+   </al-setarg>
+  <table class="detail">
+   <thead>
+    <tr>
+     <th></th>
+     <al-for iter="d_i" expr="labsurv.TestDiags.diagnoses">
+      <th><al-value expr="d_i.value()[1]" /></th>
+     </al-for>
+    </tr>
+   </thead>
+   <tbody>
+    <al-for iter="t_i" expr="report.test_diags">
+     <al-exec expr="test = t_i.value()" />
+     <tr>
+      <td class="rowlabel"><al-value expr="test.label" /></td>
+      <al-for iter="d_i" expr="test.diagnoses">
+       <td><al-input nameexpr="'report.test_diags[%d].counts[%d]' % (t_i.index(), d_i.index())" /></td>
+      </al-for>
+     </tr>
+    </al-for>
+   </tbody>
+  </table>
+  <al-setarg name="buttons">
+   <table class="buttons">
+    <tr>
+     <td align="left"><al-input type="submit" name="back" value="<< Back" /></td>
+     <td align="right"><al-input type="submit" name="next" value="Next >>" /></td>
+    </tr>
+   </table>
+  </al-setarg>
+</al-expand>
diff --git a/labsurv/pages/macros.html b/labsurv/pages/macros.html
new file mode 100644
index 0000000..edce1ac
--- /dev/null
+++ b/labsurv/pages/macros.html
@@ -0,0 +1,69 @@
+<al-require version="2">
+
+<al-macro name="page">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta http-equiv="Content-Script-Type" content="text/javascript">
+  <meta name="robots" content="noindex,nofollow">
+  <al-link rel="shortcut icon" hrefexpr="appath('images','favicon.ico')" type="image/x-icon">
+  <al-script srcexpr="appath('helpers.js')" type="text/javascript"></al-script>
+  <style type="text/css" media="all">
+   <!-- @import "/<al-value expr='appname' />/style.css"; -->
+  </style>
+  <title><al-value expr="apptitle" /></title>
+ </head>
+ <body>
+  <al-setdefault name="rtitle" />
+  <al-setdefault name="buttons" />
+  <al-form name="appform" method="post" autocomplete="off">
+   <div class="banner">
+    <al-img expr="appath('images','banner.jpg')" border="0" alt="" />
+    <al-button onclickexpr="'pophelp(%r, %r);' % (appath('help.html'), __page__)" id="help"><al-img expr="appath('images','help-g.png')" border="0" alt="Help" /></al-button>
+    <h1><al-value expr="apptitle" /></h1>
+    <div class="floatright"><al-usearg name="rtitle"></div>
+    <h2>
+     <al-if expr="report.lab">
+      <al-value expr="report.lab" /> report for week ending 
+      <al-value expr="report.week" /><br>
+     </al-if>
+     <al-usearg name="title" />
+    </h2>
+    <div class="clear"></div>
+   </div>
+   <al-if expr="msgs">
+    <al-for iter="m_i" expr="msgs">
+     <al-exec expr="lvl, msg = m_i.value()" />
+     <al-div classexpr="lvl + '-msg'"><al-value expr="msg" /></al-div>
+    </al-for>
+    <al-exec expr="msgs = []" />
+   </al-if>
+   <div class="content">
+    <al-usearg />
+    <script type="text/javascript">enterSubmit('appform', 'next');</script>
+   </div>
+   <al-usearg name="buttons" />
+  </al-form>
+ </body>
+</html>
+</al-macro>
diff --git a/labsurv/pages/submit.html b/labsurv/pages/submit.html
new file mode 100644
index 0000000..0b00172
--- /dev/null
+++ b/labsurv/pages/submit.html
@@ -0,0 +1,123 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page">
+   <al-setarg name="title">
+    Step 5: Summary
+   </al-setarg>
+   <h3>Number of specimens submitted for respiratory virus diagnosis</h3>
+   <table class="grid rtable">
+    <thead>
+     <tr>
+      <td></td>
+      <th>Count</th>
+     </tr>
+    </thead>
+    <tbody>
+     <al-for iter="t_i" expr="report.test_totals">
+      <al-exec expr="test = t_i.value()" />
+      <tr>
+       <th><al-value expr="test.label" /></th>
+       <td><al-if expr="test.count"><al-value expr="test.count" /></al-if></td>
+      </tr>
+     </al-for>
+    </tbody>
+   </table>
+   <h3>Number of diagnoses made</h3>
+   <table class="grid rtable">
+    <thead>
+     <tr>
+      <td></td>
+      <al-for iter="d_i" expr="labsurv.TestDiags.diagnoses">
+       <th><al-value expr="d_i.value()[1]" /></th>
+      </al-for>
+     </tr>
+    </thead>
+    <tbody>
+     <al-for iter="t_i" expr="report.test_diags">
+      <al-exec expr="test = t_i.value()" />
+      <tr>
+       <th><al-value expr="test.label" /></th>
+       <al-for iter="d_i" expr="test.counts">
+        <td>
+         <al-if expr="d_i.value()">
+          <al-value expr="d_i.value()" />
+         </al-if>
+        </td>
+       </al-for>
+      </tr>
+     </al-for>
+    </tbody>
+   </table>
+   <h3>Influenza positive cases this week</h3>
+   <table class="grid">
+    <col align="right">
+    <col align="left">
+    <col align="center">
+    <col align="right">
+    <col align="center">
+    <col align="left">
+    <col align="left">
+    <thead>
+     <tr>
+      <td></td>
+      <th>Test method</th>
+      <th>Flu</th>
+      <th>Age of case</th>
+      <th>Sex</th>
+      <th>Suburb</th>
+      <th>Postcode</th>
+     </tr>
+    </thead>
+    <tbody>
+     <al-for iter="c_i" expr="report.positive_cases">
+      <al-exec expr="case = c_i.value()" />
+      <al-if expr="case">
+       <tr>
+        <th><al-value expr="case.idx + 1" /></th>
+        <td><al-value expr="case.test" /></td>
+        <td><al-value expr="case.diagnosis" /></td>
+        <td><al-value expr="case.age" /></td>
+        <td><al-value expr="case.sex" /></td>
+        <td><al-value expr="case.suburb" /></td>
+        <td><al-value expr="case.postcode" /></td>
+       </tr>
+      </al-if>
+     </al-for>
+    </tbody>
+   </table>
+   <h3>Notes</h3>
+   <pre><al-value expr="report.notes" /></pre>
+   <al-setarg name="buttons">
+    <table class="buttons">
+     <tr>
+      <td align="left">
+       <al-input type="submit" name="back" value="<< Back" />
+      </td>
+      <td align="center">
+       <al-input type="button" name="print" value="Print" 
+        onclick="window.print();" />
+      </td>
+      <td align="right">
+       <al-input type="submit" name="submit" value="Submit" />
+      </td>
+     </tr>
+    </table>
+   </al-setarg>
+</al-expand>
diff --git a/labsurv/pages/totals.html b/labsurv/pages/totals.html
new file mode 100644
index 0000000..f4dec4b
--- /dev/null
+++ b/labsurv/pages/totals.html
@@ -0,0 +1,42 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page">
+   <al-setarg name="title">
+    Step 2: Number of specimens submitted for respiratory virus diagnosis
+   </al-setarg>
+  <table>
+   <al-for iter="t_i" expr="report.test_totals">
+    <al-exec expr="test = t_i.value()" />
+    <tr>
+     <td><al-label forexpr="test.name"><al-value expr="test.label" /></al-label></td>
+     <td><al-input idexpr="test.name" nameexpr="'report.test_totals[%d].count' % t_i.index()" />
+     </td>
+    </tr>
+   </al-for>
+  </table>
+  <al-setarg name="buttons">
+   <table class="buttons">
+    <tr>
+     <td align="left"><al-input type="submit" name="back" value="<< Back" /></td>
+     <td align="right"><al-input type="submit" name="next" value="Next >>" /></td>
+    </tr>
+   </table>
+  </al-setarg>
+</al-expand>
diff --git a/labsurv/schema/chown b/labsurv/schema/chown
new file mode 100755
index 0000000..688527b
--- /dev/null
+++ b/labsurv/schema/chown
@@ -0,0 +1,7 @@
+owner=$1
+cat << EOF
+ALTER TABLE lab_reports OWNER TO "${owner}";
+ALTER TABLE lab_totals OWNER TO "${owner}";
+ALTER TABLE lab_diags OWNER TO "${owner}";
+ALTER TABLE lab_cases OWNER TO "${owner}";
+EOF
diff --git a/labsurv/schema/schema.sql b/labsurv/schema/schema.sql
new file mode 100644
index 0000000..60639ee
--- /dev/null
+++ b/labsurv/schema/schema.sql
@@ -0,0 +1,80 @@
+-- 
+--  The contents of this file are subject to the HACOS License Version 1.2
+--  (the "License"); you may not use this file except in compliance with
+--  the License.  Software distributed under the License is distributed
+--  on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+--  implied. See the LICENSE file for the specific language governing
+--  rights and limitations under the License.  The Original Software
+--  is "NetEpi Collection". The Initial Developer of the Original
+--  Software is the Health Administration Corporation, incorporated in
+--  the State of New South Wales, Australia.
+-- 
+--  Copyright (C) 2004-2011 Health Administration Corporation and others. 
+--  All Rights Reserved.
+-- 
+--  Contributors: See the CONTRIBUTORS file for details of contributions.
+-- 
+
+BEGIN;
+
+-- Overview - per lab per week
+CREATE TABLE lab_reports (
+    report_id SERIAL UNIQUE PRIMARY KEY,
+    lab VARCHAR,
+    week TIMESTAMP,
+    notes VARCHAR,
+    completed TIMESTAMP
+);
+
+CREATE INDEX lr_l_idx
+    ON lab_reports (lab);
+
+CREATE UNIQUE INDEX lr_lw_idx
+    ON lab_reports (lab,week);
+
+-- Total tests performed - per lab per week per test
+CREATE TABLE lab_totals (
+    report_id INTEGER REFERENCES lab_reports ON DELETE CASCADE,
+    test VARCHAR,
+    count INTEGER
+);
+
+CREATE INDEX lt_r_idx
+    ON lab_totals (report_id);
+
+CREATE UNIQUE INDEX lt_rt_idx
+    ON lab_totals (report_id,test);
+
+
+-- Positive diagnoses - per lab per week per test per diagnosis
+CREATE TABLE lab_diags (
+    report_id INTEGER REFERENCES lab_reports ON DELETE CASCADE,
+    test VARCHAR,
+    diagnosis VARCHAR,
+    count INTEGER
+);
+
+CREATE INDEX ld_r_idx
+    ON lab_diags (report_id);
+
+CREATE UNIQUE INDEX ld_rtd_idx
+    ON lab_diags (report_id,test,diagnosis);
+
+-- Postive case details
+CREATE TABLE lab_cases (
+    report_id INTEGER REFERENCES lab_reports ON DELETE CASCADE,
+    idx INTEGER,
+    test VARCHAR,
+    diagnosis VARCHAR,
+    age FLOAT,
+    sex VARCHAR,
+    suburb VARCHAR,
+    postcode VARCHAR
+);
+CREATE INDEX lc_r_idx
+    ON lab_cases (report_id);
+
+CREATE UNIQUE INDEX lc_ri_idx
+    ON lab_cases (report_id,idx);
+
+COMMIT;
diff --git a/labsurv/schema/upgrade1.sql b/labsurv/schema/upgrade1.sql
new file mode 100644
index 0000000..8b29d7a
--- /dev/null
+++ b/labsurv/schema/upgrade1.sql
@@ -0,0 +1,10 @@
+--
+-- Merge Paraflu 1, 2 and 3 counts into "Paraflu"
+--
+BEGIN;
+INSERT INTO lab_diags (report_id, test, diagnosis, count) 
+    SELECT report_id, test, 'Paraflu', sum(count) 
+        FROM lab_diags 
+            WHERE diagnosis LIKE 'Paraflu_' 
+            GROUP BY report_id, test;
+COMMIT;
diff --git a/labsurv/simpleinst/__init__.py b/labsurv/simpleinst/__init__.py
new file mode 100644
index 0000000..25b7131
--- /dev/null
+++ b/labsurv/simpleinst/__init__.py
@@ -0,0 +1,66 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import simpleinst.defaults
+import simpleinst.platform
+import simpleinst.config_register
+import simpleinst.install_files
+import simpleinst.pyinstaller
+import simpleinst.utils
+from simpleinst.filter import Filter
+from simpleinst.utils import secret, getpass, collect, rm_pyc
+from simpleinst.usergroup import user_lookup
+
+# Config priority:
+#    10. command line
+#    20. config.py
+#    30. install.py (includes any explicitly set config attributes)
+#    40. platform defaults (from simpleinst.platform)
+#    50. defaults (from simpleinst.defaults)
+# Note that we add these in reverse order, as later, more complex configs
+# can depend on earlier ones
+config = simpleinst.config_register.Config()
+config.source(50, simpleinst.defaults.Defaults())
+config.source(40, simpleinst.platform.get_platform())
+config.source_attrs(30)
+config.source_file(20, 'config')
+config.source_cmdline(10)
+
+import os
+joinpath = os.path.join
+basename = os.path.basename
+dirname = os.path.dirname
+abspath = os.path.abspath
+normpath = os.path.normpath
+del os
+
+from py_compile import compile as _py_compile
+def py_compile(fn):
+    _py_compile(fn, doraise=True)
+
+def make_dirs(*args, **kwargs):
+    kwargs['config'] = config
+    return simpleinst.utils.make_dirs(*args, **kwargs)
+
+def install(**kwargs):
+    return simpleinst.install_files.install(config=config, **kwargs)
+
+def on_install(*args, **kwargs):
+    simpleinst.install_files.on_install(config, *args, **kwargs)
+
+def py_installer(name, *args, **kwargs):
+    return simpleinst.pyinstaller.py_installer(config, name, *args, **kwargs)
+
+python_bang_path_filter = Filter(config, pattern = '^#!.*',
+                                 subst = '#!%(python)s', count = 1)
+
+copy = simpleinst.install_files.copy
diff --git a/labsurv/simpleinst/config_register.py b/labsurv/simpleinst/config_register.py
new file mode 100644
index 0000000..8c70b64
--- /dev/null
+++ b/labsurv/simpleinst/config_register.py
@@ -0,0 +1,185 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+
+import sys
+import os
+import imp
+import tempfile
+import bisect
+import fnmatch
+from simpleinst.utils import chown, chmod, normjoin, make_dirs
+
+class ConfigBase:
+    pass
+
+
+class ConfigAttrs(ConfigBase):
+    config_source = 'Installer'
+
+
+class ConfigCmdLine(ConfigBase):
+    """
+    Parse the command line, infering types from prior config (defaults)
+    """
+    config_source = 'Command Line'
+
+    def __init__(self, config):
+        for arg in sys.argv[1:]:
+            try:
+                a, v = arg.split('=')
+            except ValueError:
+                sys.exit('Unknown command line option: %r' % arg)
+            try:
+                t = type(getattr(config, a))
+            except AttributeError:
+                pass
+            else:
+                if t is bool:
+                    v = v.lower() in ('t', 'true', 'y', 'yes', '1')
+                else:
+                    try:
+                        v = t(v)
+                    except (ValueError, TypeError):
+                        pass
+            setattr(self, a, v)
+
+
+class Config:
+    def __init__(self):
+        self._sources = []
+        self._config_attrs = ConfigAttrs()
+
+    def source(self, prio, source):
+        assert source
+        pair = prio, source
+        i = bisect.bisect(self._sources, pair)
+        self._sources.insert(i, pair)
+
+    def source_attrs(self, prio):
+        self.source(prio, self._config_attrs)
+
+    def source_file(self, prio, name='config', path='', exclude=None):
+        if not os.path.isabs(path):
+            path = normjoin(self.base_dir, path)
+        try:
+            f, filename, extras = imp.find_module(name, [path])
+        except ImportError, e:
+            return
+        config_mod = imp.load_module(name, f, filename, extras)
+        config_mod.config_source = filename
+        if exclude:
+            for attr in exclude:
+                try:
+                    delattr(config_mod, attr)
+                except AttributeError:
+                    pass
+        self.source(prio, config_mod)
+
+    def source_cmdline(self, prio):
+        self.source(prio, ConfigCmdLine(self))
+
+    def __getattr__(self, a):
+        for prio, source in self._sources:
+            try:
+                return getattr(source, a)
+            except AttributeError:
+                pass
+        raise AttributeError('attribute "%s" not found' % a)
+
+    def __setattr__(self, a, v):
+        if a.startswith('_'):
+            self.__dict__[a] = v
+        else:
+            setattr(self._config_attrs, a, v)
+
+    def _config_dict(self):
+        """
+        Produce a dictionary of the current config
+        """
+        class _ConfigItem(object):
+            __slots__ = 'value', 'source'
+
+            def __init__(self, value, source):
+                self.value = value
+                self.source = source
+
+        config = {}
+        for prio, source in self._sources:
+            for a in dir(source):
+                if not config.has_key(a) and not a.startswith('_') \
+                    and a != 'config_source':
+                    v = getattr(source, a)
+                    if not callable(v):
+                        config[a] = _ConfigItem(v, source.config_source)
+        return config
+
+    def write_file(self, filename, exclude=None, owner=None, mode=None):
+        if not exclude:
+            exclude = ()
+        config = self._config_dict()
+        if self.install_prefix:
+            filename = self.install_prefix + filename
+        target_dir = os.path.dirname(filename)
+        make_dirs(target_dir, owner=owner)
+        fd, tmpname = tempfile.mkstemp(dir=target_dir)
+        f = os.fdopen(fd, 'w')
+        attributes = config.keys()
+        attributes.sort()
+        try:
+            for a in attributes:
+                for e in exclude:
+                    if fnmatch.fnmatch(a, e):
+                        break
+                else:
+                    f.write('%s=%r\n' % (a, config[a].value))
+            f.flush()
+            if owner is not None:
+                chown(tmpname, owner)
+            if mode is not None:
+                chmod(tmpname, mode)
+            os.rename(tmpname, filename)
+        finally:
+            f.close()
+            try:
+                os.unlink(tmpname)
+            except OSError:
+                pass
+
+    def __str__(self):
+        srcs = ';'.join(['%s[%s]' % (s.config_source, p)
+                         for p, s in self._sources])
+        config = self._config_dict()
+        attrs = config.keys()
+        attrs.sort()
+        attrs = ['\n    %s=%r (from %s)' % (a,config[a].value,config[a].source) 
+                 for a in attrs]
+        return '<%s %s%s>' % (self.__class__.__name__, srcs, ''.join(attrs))
+
+class Args:
+    pass
+
+def args_with_defaults(kwargs, config, arglist, conf_prefix = ''):
+    args = Args()
+    for argname in arglist:
+        try:
+            value = kwargs[argname]
+        except KeyError:
+            try:
+                value = getattr(config, conf_prefix + argname)
+            except AttributeError:
+                try:
+                    value = getattr(config, argname)
+                except AttributeError:
+                    value = None
+        setattr(args, argname, value)
+    return args
diff --git a/labsurv/simpleinst/defaults.py b/labsurv/simpleinst/defaults.py
new file mode 100644
index 0000000..37201e6
--- /dev/null
+++ b/labsurv/simpleinst/defaults.py
@@ -0,0 +1,25 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import sys
+from os.path import dirname, abspath, normpath
+from distutils.sysconfig import get_config_var
+
+
+class Defaults:
+    config_source = 'Defaults'
+    install_mode = 0444
+    install_verbose = False
+    install_prefix = ''
+    base_dir = normpath(dirname(sys.modules['__main__'].__file__))
+    python = abspath(sys.executable)
+    bin_dir = get_config_var('BINDIR')
diff --git a/labsurv/simpleinst/filter.py b/labsurv/simpleinst/filter.py
new file mode 100644
index 0000000..df1ab2e
--- /dev/null
+++ b/labsurv/simpleinst/filter.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import re
+
+class _InstanceAsDict:
+    def __init__(self, inst):
+        self.inst = inst
+
+    def __getitem__(self, a):
+        try:
+            return getattr(self.inst, a)
+        except AttributeError:
+            raise KeyError(a)
+
+class Filter:
+    def __init__(self, config, pattern, subst, count = 0):
+        self.config = _InstanceAsDict(config)
+        self.pattern = re.compile(pattern, re.MULTILINE)
+        self.subst = subst
+        self.count = count
+
+    def filter(self, data):
+        return self.pattern.sub(self.subst % self.config, data, self.count)
diff --git a/labsurv/simpleinst/glob.py b/labsurv/simpleinst/glob.py
new file mode 100644
index 0000000..2326e45
--- /dev/null
+++ b/labsurv/simpleinst/glob.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+"""
+The standard glob doesn't support the concept of a base dir - something
+that we need in this application
+"""
+import os
+import re
+import fnmatch
+
+glob_re = re.compile('[*?[]')
+
+def glob(basedir, pattern):
+    def has_glob(pattern):
+        return glob_re.search(pattern) is not None
+
+    dirs = ['']
+    pattern_components = pattern.split(os.path.sep)
+    for pc in pattern_components:
+        new_dirs = []
+        if has_glob(pc):
+            for dir in dirs:
+                try:
+                    files = os.listdir(os.path.join(basedir, dir))
+                except OSError:
+                    continue
+                if not pattern.startswith('.'):
+                    files = [f for f in files if not f.startswith('.')]
+                new_dirs.extend([os.path.join(dir, f) 
+                                 for f in fnmatch.filter(files, pc)])
+        else:
+            for dir in dirs:
+                if os.path.exists(os.path.join(basedir, dir, pc)):
+                    new_dirs.append(os.path.join(dir, pc))
+        dirs = new_dirs
+    if not dirs:
+        return [pattern]
+    else:
+        return dirs
diff --git a/labsurv/simpleinst/install_files.py b/labsurv/simpleinst/install_files.py
new file mode 100644
index 0000000..0f32d59
--- /dev/null
+++ b/labsurv/simpleinst/install_files.py
@@ -0,0 +1,190 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import os
+import stat
+import errno
+import tempfile
+import re
+from fnmatch import fnmatch
+from simpleinst.config_register import args_with_defaults
+from simpleinst.usergroup import user_lookup
+from simpleinst.glob import glob
+from simpleinst.filter import Filter
+from simpleinst.utils import *
+
+class PostInstallAction:
+    def __init__(self, pattern, fn, args, kwargs):
+        self.pattern = pattern
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+
+    def match(self, filename):
+        return fnmatch(filename, self.pattern)
+
+    def action(self, filename, verbose):
+        if verbose:
+            print '    %s(%s)' % (self.fn.__name__, filename)
+        self.fn(filename, *self.args, **self.kwargs)
+
+class PostInstallActions:
+    def __init__(self, config):
+        self.actions = []
+        self.config = config
+
+    def add(self, pattern, fn, *args, **kwargs):
+        self.actions.append(PostInstallAction(pattern, fn, args, kwargs))
+
+    def post_install(self, filename, verbose=False):
+        for action in self.actions:
+            if action.match(filename):
+                action.action(filename, verbose=verbose)
+
+post_install_actions = None
+
+class FilenameFilter:
+    def __init__(self, include, exclude):
+        if type(include) in (str, unicode):
+            include = [include]
+        if type(exclude) in (str, unicode):
+            exclude = [exclude]
+        self.include_nop = not include
+        self.exclude_nop = not exclude
+        if not self.include_nop:
+            self.path_include = [i for i in include if i.find(os.path.sep) >= 0]
+            self.name_include = [i for i in include if i.find(os.path.sep) < 0]
+        if not self.exclude_nop:
+            self.path_exclude = [i for i in exclude if i.find(os.path.sep) >= 0]
+            self.name_exclude = [i for i in exclude if i.find(os.path.sep) < 0]
+    
+    def include(self, name):
+        basename = os.path.basename(name)
+
+        if not self.exclude_nop:
+            if basename != name:
+                for exclude in self.path_exclude:
+                    if fnmatch(name, exclude):
+                        return False
+            for exclude in self.name_exclude:
+                if fnmatch(basename, exclude):
+                    return False
+
+        if self.include_nop:
+            return True
+
+        if basename != name:
+            for include in self.path_include:
+                if fnmatch(name, include):
+                    return True
+        for include in self.name_include:
+            if fnmatch(basename, include):
+                return True
+
+        return False
+
+def copy(src, dst, 
+         owner = None, mode = None, filter = None, verbose = False,
+         bufsize = 1 << 22):
+    dst_dir = os.path.dirname(dst)
+    make_dirs(dst_dir, owner)
+    r_fd = os.open(src, os.O_RDONLY)
+    try:
+        st = os.fstat(r_fd)
+        try:
+            dst_st = os.stat(dst)
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
+        else:
+            # Same file? utime almost matches, and size matches...
+            if (abs(st.st_mtime - dst_st.st_mtime) <= 1 and
+                st.st_size == dst_st.st_size):
+                return False
+        w_fd, tmp_filename = tempfile.mkstemp(dir = dst_dir)
+        try:
+            while 1:
+                buf = os.read(r_fd, bufsize)
+                if not buf:
+                    break
+                if filter:
+                    if len(buf) == bufsize:
+                        raise IOError('Can\'t filter files larger than %s' %
+                                        bufsize - 1)
+                    for f in filter:
+                        buf = f.filter(buf)
+                os.write(w_fd, buf)
+            if mode:
+                chmod(tmp_filename, mode)
+            else:
+                os.chmod(tmp_filename, st.st_mode & 0777)
+            if owner:
+                os.chown(tmp_filename, *owner)
+            os.rename(tmp_filename, dst)
+            os.utime(dst, (st.st_atime, st.st_mtime))
+            if verbose:
+                print '    %s -> %s' % (src, dst_dir)
+            tmp_filename = None
+            return True
+        finally:
+            os.close(w_fd)
+            if tmp_filename:
+                os.unlink(tmp_filename)
+    finally:
+        os.close(r_fd)
+
+def recursive_copy(args, src):
+    fullsrc = normjoin(args.base, src)
+    src_path, src_file = os.path.split(src)
+    st = os.stat(fullsrc)
+    if stat.S_ISDIR(st.st_mode):
+        for filename in os.listdir(fullsrc):
+            recursive_copy(args, os.path.join(src, filename))
+    else:
+        if not args.filename_filter.include(src):
+            return
+        dst = normjoin(args.target, src)
+        if copy(fullsrc, dst, filter = args.filter,
+                owner = args.owner, mode = args.mode, 
+                verbose = args.verbose) and post_install_actions:
+            post_install_actions.post_install(dst, verbose=args.verbose)
+
+def install(config, **kwargs):
+    args = args_with_defaults(kwargs, config, 
+                              ('target', 'base', 'files', 'owner', 'mode', 
+                               'include', 'exclude', 'filter'),
+                              conf_prefix = 'install_')
+
+    if type(args.files) in (str, unicode):
+        args.files = [args.files]
+    if isinstance(args.filter, Filter):
+        args.filter = [args.filter]
+    if config.install_prefix:
+        args.target = config.install_prefix + args.target
+    args.verbose = getattr(config, 'install_verbose')
+    args.filename_filter = FilenameFilter(args.include, args.exclude)
+    if args.base:
+        args.base = normjoin(config.base_dir, args.base)
+    else:
+        args.base = config.base_dir
+    args.owner = user_lookup(args.owner)
+
+    for pat in args.files:
+        print 'installing %s to %s' % (normjoin(args.base, pat), args.target)
+        for src in glob(args.base, pat):
+            recursive_copy(args, src)
+
+def on_install(config, pattern, fn, *args, **kwargs):
+    global post_install_actions
+    if post_install_actions is None:
+        post_install_actions = PostInstallActions(config)
+    post_install_actions.add(pattern, fn, *args, **kwargs)
diff --git a/labsurv/simpleinst/platform.py b/labsurv/simpleinst/platform.py
new file mode 100644
index 0000000..8138e3d
--- /dev/null
+++ b/labsurv/simpleinst/platform.py
@@ -0,0 +1,65 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import sys, os
+import pwd
+
+class PlatformBase:
+    def __init__(self):
+        self.config_source = '%s platform' % self.platform
+
+class RedHatLinux(PlatformBase):
+    platform = "RedHat Linux"
+    html_dir = '/var/www/html'
+    cgi_dir = '/var/www/cgi-bin'
+    web_user = 'apache'
+
+    def is_platform(self):
+        return sys.platform == 'linux2' \
+            and os.path.exists('/etc/redhat-release')
+
+class DebianLinux(PlatformBase):
+    platform = "Debian Linux"
+    html_dir = '/var/www'
+    cgi_dir = '/usr/lib/cgi-bin'
+    web_user = 'www-data'
+
+    def is_platform(self):
+        return sys.platform == 'linux2' \
+            and os.path.exists('/etc/debian_version')
+
+class OSX(PlatformBase):
+    platform = "Apple OS X"
+    html_dir = '/Library/WebServer/Documents'
+    cgi_dir = '/Library/WebServer/CGI-Executables'
+    web_user = 'www'
+
+    def is_platform(self):
+        if sys.platform != 'darwin':
+            return False
+        # Leopard returns _www for this:
+        self.web_user = pwd.getpwnam('www').pw_name
+        return True
+
+def get_platform():
+    platforms = []
+    for name, var in globals().items():
+        if hasattr(var, 'is_platform'):
+            platform = var()
+            if platform.is_platform():
+                platforms.append(platform)
+    if not platforms:
+        sys.exit('Unrecognised playform')
+    if len(platforms) > 1:
+        sys.exit('Ambiguous platform detection: %s' % \
+                 ', '.join([p.platform for p in platforms]))
+    return platforms[0]
diff --git a/labsurv/simpleinst/pyinstaller.py b/labsurv/simpleinst/pyinstaller.py
new file mode 100644
index 0000000..b765b25
--- /dev/null
+++ b/labsurv/simpleinst/pyinstaller.py
@@ -0,0 +1,30 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+"""
+Import and run a user-defined python installer
+"""
+
+import os
+import imp
+
+def py_installer(config, name, *args, **kwargs):
+    print 'executing installer', name
+    path = os.path.join(config.base_dir, name)
+    gbals = {
+        '__name__': '__install__',
+        'config': config,
+        'args': args,
+        'kwargs': kwargs
+    }
+    return execfile(path, gbals)
+
diff --git a/labsurv/simpleinst/usergroup.py b/labsurv/simpleinst/usergroup.py
new file mode 100644
index 0000000..55f58b6
--- /dev/null
+++ b/labsurv/simpleinst/usergroup.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import pwd, grp
+import os
+
+cache = {}
+
+def user_lookup(name):
+    try:
+        return cache[name]
+    except KeyError:
+        if name:
+            try:
+                user, group = name.split(':')
+            except ValueError:
+                user, group = name, None
+        else:
+            name = str(os.geteuid())
+        try:
+            uid = int(user)
+        except ValueError:
+            pw_ent = pwd.getpwnam(user)
+            uid, gid = pw_ent.pw_uid, pw_ent.pw_gid
+        else:
+            pw_ent = pwd.getpwuid(uid)
+            uid, gid = pw_ent.pw_uid, pw_ent.pw_gid
+
+        if group:
+            try:
+                gid = int(group)
+            except ValueError:
+                gid = grp.getgrnam(group).gr_gid
+            else:
+                gid = grp.getgrgid(gid).gr_gid
+
+        cache[name] = uid, gid
+        return uid, gid
diff --git a/labsurv/simpleinst/utils.py b/labsurv/simpleinst/utils.py
new file mode 100644
index 0000000..d12dc4e
--- /dev/null
+++ b/labsurv/simpleinst/utils.py
@@ -0,0 +1,112 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import re
+import os
+import errno
+from simpleinst.usergroup import user_lookup
+
+__all__ = 'chown', 'chmod', 'normjoin', 'make_dirs'
+
+def normjoin(*args):
+    return os.path.normpath(os.path.join(*args))
+
+def chown(filename, owner):
+    if type(owner) in (unicode, str):
+        owner = user_lookup(owner)
+    os.chown(filename, *owner)
+
+chmod_re = re.compile('^([ugoa]*)([+-=])([rwxs]+)$')
+def chmod(filename, mode):
+    if type(mode) in (unicode, str):
+        if mode.startswith('0'):
+            mode = int(mode, 8)
+        else:
+            num_mode = 0400
+            for field in mode.split(','):
+                mask = 0
+                modes = 0
+                pre, mask_str, op, mode_str, post = chmod_re.split(field)
+                if mask_str:
+                    for m in mask_str:
+                        if m is 'u':
+                            mask |= 04700
+                        elif m is 'g':
+                            mask |= 02070
+                        elif m is 'o':
+                            mask |= 00007
+                        elif m is 'a':
+                            mask |= 06777
+                else:
+                    mask |= 06777
+                for m in mode_str:
+                    if m is 'r':
+                        modes |= 00444
+                    elif m is 'w':
+                        modes |= 00222
+                    elif m is 'x':
+                        modes |= 00111
+                    elif m is 's':
+                        modes |= 06000
+                if op is '+':
+                    num_mode |= modes & mask
+                elif op is '=':
+                    num_mode = modes & mask
+                elif op is '-':
+                    num_mode &= ~(modes & mask)
+            mode = num_mode
+    os.chmod(filename, mode)
+
+def make_dirs(dir, owner=None, config=None):
+    if config and config.install_prefix:
+        dir = config.install_prefix + dir
+    if type(owner) in (unicode, str):
+        owner = user_lookup(owner)
+    if not os.path.exists(dir):
+        par_dir = os.path.dirname(dir)
+        make_dirs(par_dir, owner)
+        os.mkdir(dir, 0755)
+        if owner is not None:
+            chown(dir, owner)
+
+def secret(nbits=256):
+    import binascii
+    f = open('/dev/urandom', 'rb')
+    try:
+        data = f.read(nbits / 8)
+    finally:
+        f.close()
+    return binascii.b2a_base64(data).rstrip()
+
+def getpass(prompt):
+    import getpass
+    return getpass.getpass(prompt)
+
+def collect(cmd):
+    f = os.popen(cmd, 'r')
+    try:
+        return ' '.join([l.rstrip() for l in f])
+    finally:
+        f.close()
+
+def rm_pyc(fn):
+    if fn.endswith('.py'):
+        try:
+            os.unlink(fn + 'c')
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
+        try:
+            os.unlink(fn + 'o')
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
diff --git a/labsurv/tools/pcload.py b/labsurv/tools/pcload.py
new file mode 100644
index 0000000..202a6d0
--- /dev/null
+++ b/labsurv/tools/pcload.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import csv
+
+print '''
+# Locality to postcode mapping
+# Derived from http://www1.auspost.com.au/postcodes/
+
+locality_to_postcode = {'''
+rows = list(csv.reader(open(sys.argv[1])))
+localities = {}
+for row in rows[1:]:
+    locality = row[1].replace('-', '')
+    pcode = row[0]
+    category = row[9].strip()
+    if category == 'Delivery Area' and locality not in localities:
+        # Just use first seen
+        localities[locality] = pcode
+        print '    %r: %r,' % (row[1].replace('-', ''), row[0])
+print '}'
+
diff --git a/liccheck.py b/liccheck.py
new file mode 100644
index 0000000..580fb23
--- /dev/null
+++ b/liccheck.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# This tool checks that all .py and .html files contain the following license
+# header text:
+
+"""
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+"""
+
+import sys
+import os
+import re
+
+# Check all files with these extensions
+check_exts = '.py', '.html', '.js', '.css'
+# Additional files to check
+extras = [
+    'Makefile',
+]
+# Ignore listed files
+ignore = [
+    'cocklebur/safehtml.py',            # Object Craft Licence (BSD)
+    'cocklebur/hsv.py',
+    'app/help/copyright.html',          # Contains the full Licence
+    'casemgr/phonetic_encode.py',       # ANUOS LICENCE 1.1
+    'app/lang/calendar-en.js',          # LGPL
+    'app/calendar.js',                  # LGPL
+    'app/calendar.css',                 # LGPL
+    'app/wiki.css',                     # Modified BSD
+    'casemgr/svnrev.py',                # empty file
+    'app/backiframe.html',              # trivial file
+]
+
+ignore_dirs = [
+    'simpleinst',                       # External pkg, HACOS Licence
+    'httpinteract',                     # External pkg, HACOS Licence
+    'LiveCD',                           # External pkg, HACOS Licence
+    'labsurv',                          # External pkg, HACOS Licence
+    'wiki',                             # Trac (modified BSD) licence
+    'mod_auth_pgsql-2.0.3-netepi.4101', # External pkg, Apache licence
+]
+
+filt_re = re.compile(r'(^(%|#|--|[ \t]+\*)?[ \t]*)|([ \t\r;]+$)', re.MULTILINE)
+
+exit_status = 0
+
+def strip(buf):
+    return filt_re.sub('', buf)
+
+def check(filepath, want):
+    f = open(filepath)
+    try:
+        head = f.read(2048)
+    finally:
+        f.close()
+    if want not in strip(head):
+        global exit_status
+        exit_status = 1
+        print filepath
+
+
+want = strip(__doc__)
+for filepath in extras:
+    check(filepath, want)
+for dirpath, dirnames, filenames in os.walk('.'):
+    dirnames[:] = [dirname 
+                   for dirname in dirnames 
+                   if dirname not in ignore_dirs]
+    for filename in filenames:
+        for ext in check_exts:
+            if filename.endswith(ext):
+                break
+        else:
+            continue
+        filepath = os.path.normpath(os.path.join(dirpath, filename))
+        if filepath in ignore:
+            continue
+        check(filepath, want)
+sys.exit(exit_status)
diff --git a/load/README b/load/README
new file mode 100644
index 0000000..f632fc3
--- /dev/null
+++ b/load/README
@@ -0,0 +1,155 @@
+NetEpi Collection Functional Test Facility
+==========================================
+
+NOTE - this code has not been maintained for some time, and is almost
+certainly non-functional in its current form. It has been retained to
+illustrate an alternative to the Selenium-based functional tests.
+
+LICENSE
+=======
+
+All material associated with "NetEpi Collection" is Copyright (C) 2004-2010
+Health Administration Corporation (New South Wales Department of Health).
+
+NetEpi Collection is licensed under the terms of the Health
+Administration Corporation Open Source License Version 1.2 (HACOS License
+V1.2), the full text of which can be found in the LICENSE file provided
+with NetEpi Collection.
+
+A set of functional (end-to-end) tests are under development.  Functional
+tests "exercise" the application from the user's perspective, by using
+an automated Web browser client.
+
+Two approaches are being taken. The first uses "Selenium", an open source Web
+application testing facility which uses very sophisticated Javascript to
+"drive" a Web browser to interact with the application. see the ../Selenium
+directory in the NetEpi Collection distribution for more details.
+
+An alternative approach to functional testing does not use a Web
+browser at all - the functional test programmes just pretend to be
+a Web browser by "speaking" HTTP (the protocol used by Web browsers)
+to the NetEpi Collection Web server, and checking that the expected
+responses are received. That is the approach taken by the initial
+functional test programme in this directory. 
+
+Although we plan to develop a comprehensive suite of functional tests,
+it is difficult and time-consuming to do this while the NetEpi software
+is still undergoing rapid evolution. Thus, in the current version,
+there is only one functional test provided in this directory (although
+several more are available in the ./Selenium directory).  The main use
+of the test programme provided here (formtest2.py) is for load testing -
+that is, checking the performance under load of your Web server, and also
+the characteristics (bandwidth and latency) of the network connection
+over which users will use NetEpi applications.
+
+PREREQUISITES
+=============
+
+As mentioned in the top-level README installation instructions, you need
+to have the ClientForm (http://wwwsearch.sourceforge.net/ClientForm/)
+and ClientCookie (http://wwwsearch.sourceforge.net/ClientCookie/)
+extension libraries for Python installed.
+
+In addition, you also need a source of test data. Test data must be in
+the form of a CSV (comma-separated value) file containing the following
+columns (fields) in the following order (note numbering starts at zero):
+
+    0: (ignored)
+    1: sex
+    2: given_names
+    3: surname
+    4: street_number
+    5: street_name_and_type (note columns 4 and 5 are concatenated
+       together, so all the street address data can placed in column 4
+       or column 5 if you wish)
+    6: (ignored)
+    7: locality (suburb, town)
+    8: postcode
+    9: state (as distributed, NetEpi Collection expects an Australian
+       state or territory abbreviation i.e. QLD, NSW, VIC, TAS, ACT,
+       SA, WA or NT
+    10: date of birth in yyyymmdd format
+    11: an arbitrary ID number
+
+A convenient method of generating such a file is to use the dbgen
+facility provided by the Febrl open source probabilistic record
+linkage project (see http://febrl.sf.net for more information about
+this project).  Febrl version 0.3 or later can be downloaded from
+http://sourceforge.net/project/showfiles.php?group_id=62161 You do not
+need to install it, just unpack the Febrl tarball and change to the
+dbgen subdirectory. Then issue the following command:
+
+    python generate.py testdata.csv 10000 0 0 uniform
+
+This will synthesise 10,000 dummy names and addresses and store them in
+a file called testdata.csv. You can specify numbers larger or smaller
+than 10,000 if you wish. The resulting file can be compressed using gzip
+(but not zip or other compression utilities) if you wish - the functional
+testing script can read gzipped data directly. Copy or move the test
+data file to the Collection load directory.
+
+However, dbgen does not include a sex field (something which needs to be
+fixed).  You can edit the formtest2.py programme to remove the need for
+the sex column, or you can add a sex column to the data file generated
+by dbgen, or you can use the small synthesised data file provided in
+the ./load directory.
+
+RUNNING THE TESTS
+=================
+
+Note that the test programme itself generates a significant load on
+the system on which it is running, thus is it generally better to run
+the test programme on a different machine from the Web server hosting
+NetEpi Collection (except if you merely wish to use it to load some
+dummy records for demonstration purposes).
+
+The full range of options and parameters can been seen by issuing the
+command (after changing to the load subdirectory):
+
+    python formtest2.py --help
+
+which produces the following output:
+
+    usage: Usage: formtest2.py --username=<username> 
+                    --password=<password> [options] <persons.csv> ...
+
+    options:
+    -h, --help            show this help message and exit
+    -C, --no-contacts     if given, only create cases, no contacts (contacts 
+                          creation currently disabled)
+    --sars-travel         if given, fill in a number of sars travel forms
+    --use_lynx            if given, program will use /usr/bin/lynx to extract
+                          clean HTML (Linux and Unix machines only)
+    -FLOG_FORMAT, --log-format=LOG_FORMAT
+                          log format string (default: %(asctime)s: %(levelname)s
+                            %(message)s)
+    -lLOG, --log=LOG      specify log targets
+    -q, --quiet           disable normal output
+    --retry               retry after errors
+    -UURL, --url=URL      specify application URL [default: URL]
+    -sSLEEP, --sleep=SLEEP
+                          sleep SLEEP seconds between interactions
+    --skip=SKIP           skip SKIP rows into the person data
+    -D, --debug           enable debugging
+    -uUSERNAME, --username=USERNAME
+                          username to use to login to NetEpi Collection
+    -pPASSWORD, --password=PASSWORD
+                          password to use to login to NetEpi Collection
+
+In general, you will need to specify the --url, --username and --password
+options. If lynx is available (it is on most Linux systems), then it may
+be helpful to include the --use_lynx option. The --retry option causes
+the functional tester to attempt to continue operation if an error is
+encountered (due, for instance, to a network outage or similar).
+
+Note that more than one instance of the formtest2.py script may be run
+at once, and all instances can point to the same Collection instance -
+this simulates multiple users all using the facility at once. All these
+instances of formtest2.py can use the same testdata file, but you need
+to specify a different --skip offset for each instance so that they do
+not all feed the same data to Collection.
+
+We have successfully run six instances of formtest2.py against a single
+Collection instance, loading 300,000 cases and their contacts (a
+process which took several days).
+
diff --git a/load/formtest2.py b/load/formtest2.py
new file mode 100644
index 0000000..a30e4e5
--- /dev/null
+++ b/load/formtest2.py
@@ -0,0 +1,467 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+# Standard modules
+import sys
+import re
+import csv
+import time
+import os
+import gzip
+import logging
+import traceback
+from logging import debug, info, warning, error, fatal
+from optparse import OptionParser
+from random import Random
+from datetime import datetime, timedelta
+import urllib2
+
+# http://wwwsearch.sourceforge.net/ClientForm/
+# http://wwwsearch.sourceforge.net/ClientCookie/
+import ClientForm, ClientCookie
+
+def_log_format = '%(asctime)s: %(levelname)s %(message)s'
+
+def makephone(r):
+    ph = str(r.randint(40000000,99999999))
+    return ph[0:4] + '-' + ph[4:]
+
+def xform_date(date):
+    if date:
+        year, month, day = date[:4], date[4:6], date[6:]
+        return '%s/%s/%s' % (day, month, year)
+
+def xform_person(record, form, r):
+    form["case.case_row.local_case_id"] = record['localid']
+    form["case.person.surname"] = record['surname']
+    form["case.person.given_names"] = record['given_names']
+    dob = xform_date(record['DOB'])
+    if dob != None:
+        form["case.person.DOB"] = dob
+    sex = record['sex'].upper()
+    if sex != "":
+        form["case.person.sex"] = [sex,]
+    form["case.person.home_phone"] = makephone(r)
+    form["case.person.work_phone"] = makephone(r)
+    form["case.person.street_address"] = record['street_address']
+    form["case.person.locality"] = record['locality']
+    state = record['state'].upper()
+    if state != "":
+        form["case.person.state"] = [state,]
+    form["case.person.postcode"] = record['postcode']
+    onset_dt = datetime.now() - timedelta(r.uniform(1,64))
+    onset_ymd = str(onset_dt).split()[0].split('-')
+    onset_hms = str(onset_dt).split()[1].split(":")
+    form["case.case_row.onset_datetime"] = '%s/%s/%s %s:%s' % (onset_ymd[2], onset_ymd[1], onset_ymd[0], onset_hms[0], onset_hms[1])
+    return form
+
+def xform_contact(record, form, r):
+    form["contact.person.surname"] = record['surname']
+    form["contact.person.given_names"] = record['given_names']
+    dob = xform_date(record['DOB'])
+    if dob != None:
+        form["contact.person.DOB"] = dob
+    sex = record['sex'].upper()
+    if sex != "":
+        form["contact.person.sex"] = [sex,]
+    form["contact.person.home_phone"] = makephone(r)
+    form["contact.person.work_phone"] = makephone(r)
+    form["contact.person.street_address"] = record['street_address'].title()
+    form["contact.person.locality"] = record['locality'].upper()
+    state = record['state'].upper()
+    if state != "":
+        form["contact.person.state"] = [state,]
+    form["contact.person.postcode"] = record['postcode']
+    form["contact.contact_row.contact_date"] = "21/08/2003 12:01"
+    contact_dt = datetime.now() - timedelta(r.uniform(1,64))
+    contact_ymd = str(contact_dt).split()[0].split('-')
+    contact_hms = str(contact_dt).split()[1].split(":")
+    form["contact.contact_row.contact_date"] = '%s/%s/%s %s:%s' % (contact_ymd[2], contact_ymd[1], contact_ymd[0], contact_hms[0], contact_hms[1])
+    return form
+    
+def sars_travel(form, r):
+    form["form_sars_travel_00000.country"] = 'China'
+    form["form_sars_travel_00000.arrival_date"] = '21/08/2003'
+    form["form_sars_travel_00000.arrival_flight"] = 'CN2304'
+    form["form_sars_travel_00000.departure_date"] = '25/08/2003'
+    form["form_sars_travel_00000.departure_flight"] = 'CN0121'
+    form["form_sars_travel_00000.duration"] = '4'
+    return form
+
+def rtfu(r):
+    return [r.choice(["True","False","Unknown"]),]
+
+def rmfu(r):
+    return r.choice(["M","F","U"])
+
+def xform_contact_followup(form, r):
+    f = str(form)
+    # print f
+    formname = f[f.index("form_sars_followup_"):].split(".")[0]
+    form[formname + ".first_temperature"] = str(r.normalvariate(36.7,1.0))
+    form[formname + ".second_temperature"] = str(r.normalvariate(36.7,1.0))
+    form[formname + ".antipyretic_medication"] = rtfu(r)
+    form[formname + ".malaise"] = rtfu(r)
+    form[formname + ".chills"] = rtfu(r)
+    form[formname + ".rigors"] = rtfu(r)
+    form[formname + ".headache"] = rtfu(r)
+    form[formname + ".cough"] = rtfu(r)
+    form[formname + ".diarrhoea"] = rtfu(r)
+    form[formname + ".toilet_isolation"] = rtfu(r)
+    form[formname + ".breathing_difficulty"] = rtfu(r)
+    form[formname + ".other_household_symptoms"] = rtfu(r)
+    form[formname + ".away_from_home"] = rtfu(r)
+    form[formname + ".compliance_issues"] = rtfu(r)
+    form[formname + ".other_comments"] = "The quality of mercy is not strained..."
+    return form
+    
+class Person:
+    def __init__(self, row, r):
+        self.sex = row[1].strip()
+        self.given_names = row[2].strip()
+        self.surname = row[3].strip()
+        self.street_address = row[4].strip() + ' ' + row[5].strip()
+        self.locality = row[7].strip()
+        self.postcode = row[8].strip()
+        self.state = row[9].strip()
+        self.DOB = row[10].strip()
+        self.localid = row[11].strip()
+        
+    def __getitem__(self, i):
+        return getattr(self, i)
+        
+class PersonsSource:
+    """
+    Read CSV files containing dummy person details, return them
+    one person at a time.
+    """
+    def __init__(self, files):
+        self.files = files
+        self.files_iter = iter(self.files)
+        self.row_iter = None
+        self.r = Random()
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        while 1:
+            if self.row_iter is None:
+                filename = self.files_iter.next()
+                if filename.endswith('.gz'):
+                    f = gzip.GzipFile(filename)
+                else:
+                    f = open(filename)
+                self.row_iter = csv.reader(f)
+                self.row_iter.next()            # Eat header
+            try:
+                row = Person(self.row_iter.next(),self.r)
+                # print row.sex # debug
+                if row.surname:
+                    return row
+            except StopIteration:
+                self.row_iter = None
+
+class ResponseRecorder:
+    def __init__(self, response):
+        self._response = response
+        self._data = []
+
+    def read(self, *size):
+        buf = self._response.read(*size)
+        self._data.append(buf)
+        return buf
+
+    def get_buffer(self):
+        return ''.join(self._data)
+
+    def __getattr__(self, a):
+        if a.startswith('_'):
+            raise AttributeError(a)
+        return getattr(self._response, a)
+
+class InteractError(Exception): pass
+
+class FormInteract:
+    time_history = 10
+    def __init__(self, url, debug = False, use_lynx=True):
+        self.url = url
+        self.default_form = None
+        self.forms = None
+        self.count = 0
+        self.times = []
+        self.debug = debug
+        self.use_lynx = use_lynx
+
+    def get(self):
+        self._get(self.url)
+
+    def _get(self, req):
+        starttime = time.time()
+        try:
+            self.response = ResponseRecorder(ClientCookie.urlopen(req))
+        except urllib2.HTTPError, e:
+            # urllib2.HTTPError instances are also Response objects
+            self.response = ResponseRecorder(e)
+            self.response.read()
+            try:
+                url = req.get_full_url()
+            except AttributeError:
+                url = req
+            raise InteractError('%s: %s' % (url, e))
+        self.forms = ClientForm.ParseResponse(self.response)
+        self.default_form = 0
+        interact_time = time.time() - starttime
+        self.times = [interact_time] + self.times[:self.time_history - 1]
+#        info(' interaction took %.3fs' % interact_time)         # XXX
+        self.count += 1
+
+    def av_time(self):
+        return sum(self.times) / len(self.times)
+
+    def read(self):
+        return self.response
+
+    def set_current_form(self, n):
+        assert n < len(self.forms)
+        self.default_form = n
+
+    def current_form(self):
+        return self.forms[self.default_form]
+
+    """
+    def __setitem__(self, n, v):
+        self.current_form()[n] = v
+
+    def __getitem__(self, n):
+        return self.current_form()[n]
+
+    def set(n, v):
+        self.current_form().set(n, v)
+    """
+    def __contains__(self, n):
+        try:
+            self[n]
+        except AttributeError:
+            return False
+        else:
+            return True
+
+    def __getattr__(self, a):
+        if self.forms:
+            return getattr(self.current_form(), a)
+        else:
+            raise AttributeError(a)
+
+    def __setitem__(self, i, v):
+        try:
+            self.current_form()[i] = v
+        except ClientForm.ControlNotFoundError, e:
+            raise InteractError(str(e))
+
+    def click(self, button_name):
+        try:
+            self._get(self.current_form().click(button_name))
+        except ClientForm.ControlNotFoundError, e:
+            raise InteractError(str(e))
+
+    def expect(self, regexp):
+        response_text = self.response.get_buffer()
+        if not re.search(regexp, response_text, re.IGNORECASE + re.MULTILINE):
+            raise InteractError('Expected: %r' % regexp)
+
+    def dump_response(self):
+        response_text = self.response.get_buffer()
+        if self.use_lynx:
+            try:
+                 w = os.popen('/usr/bin/lynx -stdin -dump', 'w')
+                 w.write(response_text)
+                 w.close()
+                 return
+            except:
+                pass
+        info('Last response:\n' + response_text)
+
+def random_sleep(r, interval):
+    if interval:
+        time.sleep(r.gauss(interval, interval / 6))
+
+def main():
+    optparse = OptionParser(usage='Usage: %prog --username=<username> --password=<password> [options] <persons.csv> ...')
+    optparse.add_option('-C', '--no-contacts', 
+                        dest='no_contacts', action='store_true',
+                        help='if given, only create cases, no contacts')
+    optparse.add_option('--sars-travel', 
+                        dest='sars_travel', action='store_true',
+                        help='if given, fill in a number of sars travel forms')
+    optparse.add_option('--use_lynx', 
+                        dest='use_lynx', action='store_true',
+                        help='if given, program will use /usr/bin/lynx to extract clean HTML')
+    optparse.add_option('-F', '--log-format', dest='log_format',
+                        default=def_log_format,
+                        help='log format string (default: %s)' % def_log_format)
+    optparse.add_option('-l', '--log', dest='log', action='append',
+                        help='specify log targets')
+    optparse.add_option('-q', '--quiet', dest='quiet', action='store_true',
+                        help='disable normal output')
+    optparse.add_option('--retry', dest='retry', action='store_true',
+                        help='retry after errors')
+    optparse.add_option('-U', '--url', dest='url',
+                        default='http://127.0.0.1/cgi-bin/casemgr/app.py',
+                        help='specify application URL [default: URL]')
+    optparse.add_option('-s', '--sleep', dest='sleep', type='float',
+                        help='sleep SLEEP seconds between interactions')
+    optparse.add_option('--skip', dest='skip', type='int',
+                        help='skip SKIP rows into the person data')
+    optparse.add_option('-D', '--debug', dest='debug', action='store_true',
+                        help='enable debugging')
+    optparse.add_option('-u', '--username', dest='username',default = "",
+                        help='username to use to login to NetEpi Collection')
+    optparse.add_option('-p', '--password', dest='password', default="",
+                        help='password to use to login to NetEpi Collection')
+
+    options, args = optparse.parse_args()
+    if not args:
+        optparse.error('Must specify at least one person.csv file')
+
+    if len(options.username) == 0 or len(options.password) == 0:
+        optparse.error('Must specify username= and password= paramaters')
+
+    root_logger = logging.getLogger()
+    if options.debug:
+        root_logger.setLevel(logging.DEBUG)
+    else:
+        root_logger.setLevel(logging.INFO)
+    formatter = logging.Formatter(options.log_format)
+    if not options.quiet:
+        handler = logging.StreamHandler()
+        handler.setFormatter(formatter)
+        root_logger.addHandler(handler)
+    if options.log:
+        for log in options.log:
+            handler = logging.FileHandler(log)
+            handler.setFormatter(formatter)
+            root_logger.addHandler(handler)
+
+    r = Random()
+    total_count = 0
+    restart_count = 0
+    persons = PersonsSource(args)
+    if options.skip:
+        n = 0
+        for n, record in enumerate(persons):
+            if n + 1 == options.skip:
+                break
+        else:
+            sys.exit('Can\'t skip %d rows - only %d rows of persons' %\
+                     (options.skip, n))
+    while 1:
+        interact = FormInteract(options.url, debug = options.debug, use_lynx = options.use_lynx)
+        try:
+            interact.get()
+            interact["username"] = options.username
+            interact["password"] = options.password
+            interact.click('login')
+            interact.expect('Welcome')
+            info('Logged in')
+
+            for case_count in xrange(1,100000):
+                i_count = interact.count
+                record = persons.next()
+                case_start = time.time()
+                interact.click('new:2')
+                interact.click('do_search')
+                interact.click('new_case')
+                xform_person(record, interact, r)
+                interact.click('update')
+                total_count += 1
+                info("add case took %.2fs, %d/%d(%d) cases" % \
+                     ((time.time() - case_start), case_count, 
+                      total_count, restart_count))
+                random_sleep(r, options.sleep)
+                if options.sars_travel:
+                    for sars_travel_count in range(1, r.randint(2, 5)):
+                        interact.click('new:sars_travel')
+                        form_start = time.time()
+                        sars_travel(interact, r)
+                        interact.click('form_submit')
+                        info('  %5d - added a form, took %.2f seconds' % \
+                            (sars_travel_count, time.time() - form_start))
+                # Unable to get contacts creation working reliably - bug in FormClient??
+                # It works fine interactively and with Selenium!!!
+                if False:
+                # if not options.no_contacts:
+                    interact.click('contacts')
+                    # for contact_count in range(1, r.randint(2,5)):
+                    for contact_count in range(1,2):
+                        contact_start = time.time()
+                        interact.click('add_contact')
+                        interact.click('do_search')
+                        interact.click('new_contact')
+                        record = persons.next()
+                        xform_contact(record, interact, r)
+                        interact.click('update')
+                        interact.click('back')
+                        info('  %5d - added a contact, took %.2f seconds' % \
+                            (contact_count, time.time() - contact_start))
+                        random_sleep(r, options.sleep)
+                interact.click('action')
+                info('    total time %.2f, %d/%d interactions, %.2fs av' % \
+                     ((time.time() - case_start), interact.count - i_count, 
+                      interact.count, interact.av_time()))
+        except (KeyboardInterrupt, StopIteration):
+            sys.exit(0)
+        except:
+            error('\n'.join(traceback.format_exception(*sys.exc_info())))
+            interact.dump_response()
+            ClientCookie.install_opener(None)   # Should discard CookieJar
+        if not options.retry:
+            break
+        restart_count += 1
+        info('Exception thrown (%d times so far), sleeping 20 seconds' % restart_count)
+        time.sleep(20)
+        
+            
+if __name__ == '__main__':
+    main()
+
+"""
+# Not currently used
+for t in range(10):
+    form = do_click(form,"new")[0]
+    f = str(form)
+    formname = f[f.index("form_sars_followup_"):].split(".")[0]
+    form[formname + ".first_temperature"] = "37.3"
+    form[formname + ".second_temperature"] = "37.8"
+    form[formname + ".antipyretic_medication"] = ["True",]
+    form[formname + ".malaise"] = ["True",]
+    form[formname + ".chills"] = ["False",]
+    form[formname + ".rigors"] = ["Unknown",]
+    form[formname + ".headache"] = ["False",]
+    form[formname + ".cough"] = ["True",]
+    form[formname + ".diarrhoea"] = ["True",]
+    form[formname + ".toilet_isolation"] = ["False",]
+    form[formname + ".breathing_difficulty"] = ["Unknown",]
+    form[formname + ".other_household_symptoms"] = ["True",]
+    form[formname + ".away_from_home"] = ["None",]
+    form[formname + ".compliance_issues"] = ["False",]
+    form[formname + ".other_comments"] = "The quality of mercy is not strained..."
+    # print form
+    form = do_click(form,"form_submit")[0]
+# print form
+
+"""
diff --git a/load/testdata.csv.gz b/load/testdata.csv.gz
new file mode 100644
index 0000000..8d6de2b
Binary files /dev/null and b/load/testdata.csv.gz differ
diff --git a/mail/exception_notify b/mail/exception_notify
new file mode 100644
index 0000000..e359a2d
--- /dev/null
+++ b/mail/exception_notify
@@ -0,0 +1,4 @@
+To: {{exception_notify}}
+Subject: [{{appname}}] Uncaught exception report ({{datetime}})
+
+{{exception}}
diff --git a/mail/register_invite b/mail/register_invite
new file mode 100644
index 0000000..fb0cb11
--- /dev/null
+++ b/mail/register_invite
@@ -0,0 +1,11 @@
+To: "{{fullname}}" <{{email}}>
+Subject: Invitation to use {{apptitle}}
+
+Your registration key is:
+
+   {{enable_key}}
+
+When registering to use the application, enter this key when prompted, or
+click on this link:
+
+   {{url}}
diff --git a/mail/registration_notify b/mail/registration_notify
new file mode 100644
index 0000000..79caeb9
--- /dev/null
+++ b/mail/registration_notify
@@ -0,0 +1,14 @@
+To: {{registration_notify}}
+Subject: [{{appname}}] New user registration for {{fullname}}
+
+Received registration request:
+
+    Instance:     {{apptitle}} ({{appname}})
+    Sponsor:      {{sponsor}}
+    Username:     {{username}}
+    Fullname:     {{fullname}}
+    Title:        {{title}}
+    Agency:       {{agency}}
+    Work Phone:   {{phone_work}}
+    Mobile Phone: {{phone_mobile}}
+    e-mail:       {{email}}
diff --git a/mail/too_many_attempts b/mail/too_many_attempts
new file mode 100644
index 0000000..b86404e
--- /dev/null
+++ b/mail/too_many_attempts
@@ -0,0 +1,12 @@
+To: {{registration_notify}}
+Subject: [{{apptitle}}] locked user {{username}} - too many bad passwords
+
+The following account was locked due to too many login attempts with an
+incorrect password:
+
+    Instance:     {{appname}} / {{apptitle}}
+    Username:     {{username}}
+    Fullname:     {{fullname}}
+    Work Phone:   {{phone_work}}
+    Mobile Phone: {{phone_mobile}}
+    e-mail:       {{email}}
diff --git a/pages/__init__.py b/pages/__init__.py
new file mode 100644
index 0000000..8312b3d
--- /dev/null
+++ b/pages/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
diff --git a/pages/admin.html b/pages/admin.html
new file mode 100644
index 0000000..d792c0b
--- /dev/null
+++ b/pages/admin.html
@@ -0,0 +1,364 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="search_err">
+   <tr><td></td><td colspan="3" class="err"><al-usearg /></td></tr>
+</al-macro>
+
+<al-macro name="rights_admin">
+  <tbody>
+   <tr>
+    <td class="op">Rights</td>
+    <td class="ilabel">
+     <label for="view_rights">view rights</label>
+    </td>
+    <td>
+     <al-select id="view_right" name="view_right"
+      optionexpr="rights_options" style="width: 100%;" />
+    </td>
+    <td></td>
+    <td class="buttons">
+     <al-input id="view_rights" name="view_rights" class="smallbutt" type="submit" value="View" />
+    </td>
+   </tr>
+  </tbody>
+</al-macro>
+
+<al-macro name="user_admin">
+  <tbody>
+   <tr>
+    <td rowspan="5" class="op">User</td>
+    <td class="ilabel">
+     <label for="admin_search.user.name">by user name</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.user.name" name="admin_search.user.name" 
+      class="inputs" type="text" />
+    </td>
+    <td rowspan="4" class="iopts">
+     <label><al-input name="admin_search.user.enabled" type="radio" 
+      id="admin_search.user.enabled" value="enabled" />Enabled</label><br>
+     <label><al-input name="admin_search.user.enabled" type="radio" 
+      id="admin_search.user.disabled" value="disabled" />Disabled</label><br>
+     <label><al-input name="admin_search.user.enabled" type="radio" 
+      id="admin_search.user.both" value="either" />Either</label><br>
+     <label><al-input name="admin_search.user.enabled" type="radio" 
+      id="admin_search.user.deleted" value="deleted" />Deleted</label><br>
+    </td>
+    <td rowspan="4" class="buttons">
+     <al-input name="search:user" class="smallbutt" type="submit" value="Find" /><br />
+     <al-input name="new:user" class="smallbutt" type="submit" value="New" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.user.fullname">by full name</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.user.fullname" name="admin_search.user.fullname" 
+      class="inputs" type="text" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.user.title">by title</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.user.title" name="admin_search.user.title" 
+      class="inputs" type="text" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.user.unit">by <al-value expr="config.unit_label.lower()" /></label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.user.unit" name="admin_search.user.unit" 
+      class="inputs" type="text" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.user.sponsor">by sponsor</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.user.sponsor" name="admin_search.user.sponsor" 
+      class="inputs" type="text" />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.user.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="unit_admin">
+  <tbody>
+   <tr>
+    <td class="op"><al-value expr="config.unit_label" /></td>
+    <td class="ilabel">
+     <label for="admin_search.unit.name">by <al-value expr="config.unit_label.lower()" /> name</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.unit.name" name="admin_search.unit.name" type="text" />
+    </td>
+    <td class="iopts">
+     <label><al-input name="admin_search.unit.enabled" type="radio" 
+      id="admin_search.unit_enabled" value="enabled" />Enabled</label><br />
+     <label><al-input name="admin_search.unit.enabled" type="radio" 
+      id="admin_search.unit_disabled" value="disabled" />Disabled</label><br />
+     <label><al-input name="admin_search.unit.enabled" type="radio" 
+      id="admin_search.unit_both" value="either" />Either</label>
+    </td>
+    <td class="buttons">
+     <al-input name="search:unit" class="smallbutt" type="submit" value="Find" /><br />
+     <al-input name="new:unit" class="smallbutt" type="submit" value="New" />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.unit.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="group_admin">
+  <tbody>
+   <tr>
+    <td class="op">
+      <al-value expr="config.group_label" />
+    </td>
+    <td class="inputs" colspan="2">
+     <al-select id="admin_search.group_id" name="admin_search.group_id"
+      optionexpr="group_options" style="width: 100%;" />
+    </td>
+    <td></td>
+    <td class="buttons">
+     <al-input name="search:group" class="smallbutt" type="submit" value="Edit" /><br />
+     <al-input name="new:group" class="smallbutt" type="submit" value="New" /><br />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.group.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="queue_admin">
+  <tbody>
+   <tr>
+    <td class="op">Task Queue</td>
+    <td class="ilabel">
+     <label for="admin_search.queue.name">by queue name</label>
+    </td>
+    <td class="inputs">
+      <al-input id="admin_search.queue.name" name="admin_search.queue.name" type="text" />
+    </td>
+    <td></td>
+    <td class="buttons">
+     <al-input name="search:queue" class="smallbutt" type="submit" value="Find" /><br />
+     <al-input name="new:queue" class="smallbutt" type="submit" value="New" /><br />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.queue.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="form_admin">
+  <tbody>
+   <tr>
+    <td rowspan="2" class="op">Form</td>
+    <td class="ilabel">
+     <label for="admin_search.form.form">by form name</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.form.form" name="admin_search.form.form" type="text" />
+    </td>
+    <td rowspan="2"></td>
+    <td rowspan="2" class="buttons">
+     <al-input name="search:form" class="smallbutt" type="submit" value="Find" /><br />
+     <al-input name="new:form" class="smallbutt" type="submit" value="New" /><br />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.form.syndrome_id">by <al-value expr="config.syndrome_label.lower()" /></label>
+    </td>
+    <td class="inputs">
+     <al-select id="admin_search.form.syndrome_id" 
+              name="admin_search.form.syndrome_id" 
+        optionexpr="synd_options" />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.form.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="syndrome_admin">
+  <tbody>
+   <tr>
+    <td class="op"><al-value expr="config.syndrome_label" /></td>
+    <td class="ilabel">
+     <label for="admin_search.syndrome.name">by name</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.syndrome.name" name="admin_search.syndrome.name" type="text" />
+    </td>
+    <td class="iopts">
+     <label><al-input name="admin_search.syndrome.enabled" type="radio" 
+      id="admin_search.syndrome.enabled" value="enabled" />Enabled</label><br>
+     <label><al-input name="admin_search.syndrome.enabled" type="radio" 
+      id="admin_search.syndrome.disabled" value="disabled" />Disabled</label><br>
+     <label><al-input name="admin_search.syndrome.enabled" type="radio" 
+      id="admin_search.syndrome.both" value="either" />Either</label><br>
+    </td>
+    <td class="buttons">
+     <al-input name="search:syndrome" class="smallbutt" type="submit" value="Find" /><br>
+     <al-input name="new:syndrome" class="smallbutt" type="submit" value="New" />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.syndrome.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="bulletin_admin">
+  <tbody>
+   <tr>
+    <td rowspan="3" class="op">Bulletin</td>
+    <td class="ilabel">
+     <label for="admin_search.bulletin.title">by title/synopsis</label>
+    </td>
+    <td class="inputs">
+      <al-input id="admin_search.bulletin.title" name="admin_search.bulletin.title" type="text" />
+    </td>
+    <td rowspan="3"></td>
+    <td rowspan="3" class="buttons">
+     <al-input name="search:bulletin" class="smallbutt" type="submit" value="Find" /><br />
+     <al-input name="new:bulletin" class="smallbutt" type="submit" value="New" /><br />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.bulletin.before">posted before</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.bulletin.before" name="admin_search.bulletin.before" 
+      type="text" calendarformatexpr="datetime.mx_parse_datetime.format" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel">
+     <label for="admin_search.bulletin.after">posted after</label>
+    </td>
+    <td class="inputs">
+     <al-input id="admin_search.bulletin.after" name="admin_search.bulletin.after" 
+      type="text" calendarformatexpr="datetime.mx_parse_datetime.format" />
+    </td>
+   </tr>
+   <al-expand name="search_err"><al-value expr="admin_search.bulletin.error" /></al-expand>
+  </tbody>
+</al-macro>
+
+<al-macro name="sys_admin">
+  <tbody>
+   <tr>
+    <td rowspan="6" class="op">System</td>
+    <td class="ilabel" colspan="3">
+     <label for="demog_fields">demographic fields</label>
+    </td>
+    <td class="buttons">
+     <al-input id="demog_fields" name="demog_fields" class="smallbutt" type="submit" value="Edit" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel" colspan="3">
+     <label for="contact_types">contact types</label>
+    </td>
+    <td class="buttons">
+     <al-input id="contact_types" name="contact_types" class="smallbutt" type="submit" value="Edit" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel" colspan="3">
+     <label for="case_status">case status</label>
+    </td>
+    <td class="buttons">
+     <al-input id="case_status" name="case_status" class="smallbutt" type="submit" value="Edit" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel" colspan="3">
+     <label for="case_assignment">case assignment</label>
+    </td>
+    <td class="buttons">
+     <al-input id="case_assignment" name="case_assignment" class="smallbutt" type="submit" value="Edit" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel" colspan="3">
+     <label for="address_states">states</label>
+    </td>
+    <td class="buttons">
+     <al-input id="address_states" name="address_states" class="smallbutt" type="submit" value="Edit" />
+    </td>
+   </tr>
+   <tr>
+    <td class="ilabel" colspan="3">
+     <label for="system_log">system log</label>
+    </td>
+    <td class="buttons">
+     <al-input id="system_log" name="system_log" class="smallbutt" type="submit" value="View" />
+    </td>
+   </tr>
+  </tbody>
+</al-macro>
+
+<al-macro name="admin_line">
+   <tr><td class="spacer" colspan="5"></td></tr>
+</al-macro>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Menu</al-setarg>
+  <table border="0" class="admin-m">
+   <al-expand name="group_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="syndrome_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="form_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="unit_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="user_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="rights_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="queue_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="bulletin_admin" />
+   <al-expand name="admin_line" />
+   <al-expand name="sys_admin" />
+   <al-expand name="admin_line" />
+   <tr>
+    <td colspan="4" align="center">
+     <div class="smaller">
+      Logged in as <al-value expr="_credentials.user.fullname">. For
+      assistance, please contact <al-value expr="helpdesk_contact" noescape />.
+     </div>
+    </td>
+    <td class="buttons">
+     <al-input name="reset" class="smallbutt" type="submit" value="Clear" />
+    </td>
+   </tr>
+  </table>
+  <script>enterBodySubmit('appform');</script>
+</al-expand>
diff --git a/pages/admin.py b/pages/admin.py
new file mode 100644
index 0000000..508cd23
--- /dev/null
+++ b/pages/admin.py
@@ -0,0 +1,88 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import datetime
+from casemgr import globals, syndrome, logview, rights
+from casemgr.casestatus import EditSyndromeCaseStates
+from casemgr.caseassignment import EditSyndromeCaseAssignment
+from casemgr.admin.search import Searches
+from pages import page_common
+import config
+
+# NOTE: Much of the logic of this page is implemented in the
+# casemgr.admin.search module.
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_demog_fields(self, ctx, ignore):
+        ctx.push_page('admin_synd_fields', None)
+
+    def do_case_status(self, ctx, ignore):
+        ctx.push_page('admin_synd_categ', EditSyndromeCaseStates(None))
+
+    def do_case_assignment(self, ctx, ignore):
+        ctx.push_page('admin_synd_categ', EditSyndromeCaseAssignment(None))
+
+    def do_address_states(self, ctx, ignore):
+        ctx.push_page('admin_address_states')
+
+    def do_contact_types(self, ctx, ignore):
+        ctx.push_page('admin_contact_types')
+
+    def do_new(self, ctx, search_name):
+        ctx.locals.admin_search.new(ctx, search_name, 
+                                    ctx.locals._credentials.prefs)
+
+    def do_search(self, ctx, search_name):
+        ctx.locals.admin_search.search(ctx, search_name, 
+                                       ctx.locals._credentials.prefs)
+
+    def do_reset(self, ctx, ignore):
+        ctx.locals.admin_search.reset()
+
+    def do_system_log(self, ctx, ignore):
+        log = logview.SystemLogView(ctx.locals._credentials.prefs, 'System log')
+        ctx.push_page('logview', log)
+
+    def do_view_rights(self, ctx, ignore):
+        ctx.push_page('admin_view_right', ctx.locals.view_right)
+
+pageops = PageOps()
+
+
+def page_enter(ctx):
+    ctx.locals.admin_search = Searches()
+    ctx.locals.form_cutbuff = None
+    ctx.locals.feq_cutbuff = None
+    ctx.add_session_vars('admin_search', 'form_cutbuff', 'feq_cutbuff')
+
+def page_leave(ctx):
+    ctx.del_session_vars('admin_search', 'form_cutbuff', 'feq_cutbuff')
+
+def page_display(ctx):
+    query = globals.db.query('groups', order_by='group_name')
+    ctx.locals.group_options = query.fetchcols(('group_id', 'group_name'))
+    ctx.locals.group_options.insert(0, ('', 'Any'))
+    ctx.locals.synd_options = syndrome.syndromes.optionexpr()
+    ctx.locals.synd_options.insert(0, ('', ''))
+    ctx.locals.rights_options = rights.available.options
+    ctx.run_template('admin.html')
+
+def page_process(ctx):
+    ctx.locals.admin_search.clear_errors()
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_address_states.html b/pages/admin_address_states.html
new file mode 100644
index 0000000..2a7f723
--- /dev/null
+++ b/pages/admin_address_states.html
@@ -0,0 +1,67 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+<al-include name="group_edit.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Address States</al-setarg>
+  <table border="0" class="admin-syndromes">
+   <thead>
+    <tr>
+     <th>Code</th>
+     <th>Label</th>
+     <th>
+      <al-input type="image" height="24" width="24" alt="add"
+        srcexpr="appath('images/button-add.png')" nameexpr="'add:'" />
+     </th>
+    </tr>
+   </thead>
+   <tfoot>
+    <tr>
+     <td colspan="4" class="darker">
+      <al-expand name="confirm_or_error">
+       <table width="100%" cellpadding="0" cellspacing="0">
+        <tr>
+         <td align="right">
+          <al-input class="butt" name="update" type="submit" value="Save" />
+          <script>enterSubmit('appform', 'update');</script>
+         </td>
+        </tr>
+       </table>
+      </al-expand>
+     </td>
+    </tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="cs_i" expr="address_states">
+     <tr>
+      <td><al-input nameexpr="'address_states[%d].code' % cs_i.index()" /></td>
+      <td><al-input nameexpr="'address_states[%d].label' % cs_i.index()" /></td>
+      <td>
+       <al-input type="image" height="24" width="24" alt="del"
+         srcexpr="appath('images/button-del.png')" 
+         nameexpr="'del:%d' % cs_i.index()" />
+      </td>
+     </tr>
+    </al-for>
+   </tbody>
+  </table>
+</al-expand>
diff --git a/pages/admin_address_states.py b/pages/admin_address_states.py
new file mode 100644
index 0000000..06090ce
--- /dev/null
+++ b/pages/admin_address_states.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, addressstate
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsLeaf):
+    def unsaved_check(self, ctx):
+         if ctx.locals.address_states.has_changed():
+            ctx.add_error('WARNING: address state changes discarded')
+
+    def commit(self, ctx):
+        if ctx.locals.address_states.has_changed():
+            ctx.locals.address_states.update()
+            globals.db.commit()
+            globals.notify.notify('address_states')
+        ctx.add_message('Updated states')
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_del(self, ctx, index):
+        ctx.locals.address_states.delete(int(index))
+
+    def do_add(self, ctx, ignore):
+        ctx.locals.address_states.new()
+
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    ctx.locals.address_states = addressstate.EditAddressStates()
+    ctx.add_session_vars('address_states')
+
+def page_leave(ctx):
+    ctx.del_session_vars('address_states')
+
+def page_display(ctx):
+    ctx.run_template('admin_address_states.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_bulletin.html b/pages/admin_bulletin.html
new file mode 100644
index 0000000..ff6c81b
--- /dev/null
+++ b/pages/admin_bulletin.html
@@ -0,0 +1,81 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="group_edit.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Bulletin</al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <th colspan="2">Edit bulletin</th>
+   </tr>
+   <tr>
+    <td align="right">Title:</td>
+    <td><al-input name="bulletin.title" size="60" /></td>
+   </tr>
+   <tr>
+    <td align="right">Post Date:</td>
+    <td><al-input name="bulletin.post_date" size="60" id="bulletin.post_date" 
+     calendarformatexpr="datetime.mx_parse_datetime.format" /></td>
+   </tr>
+   <tr>
+    <td align="right" valign="baseline">Expiry Date:</td>
+    <td><al-input name="bulletin.expiry_date" size="60" 
+     calendarformatexpr="datetime.mx_parse_datetime.format" />
+     <div class="smaller">(leave blank for "Never")</div>
+    </td>
+   </tr>
+   <tr>
+    <td align="right"><al-value expr="config.group_label" />:</td>
+    <td>
+     <al-expand name="group_edit" />
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Synopsis:</td>
+    <td>
+     <al-textarea name="bulletin.synopsis" cols="60" rows="4">
+      <al-value expr="bulletin.synopsis" />
+     </al-textarea>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Detail:<br /><al-input class="butt" name="wikiedit:detail" type="submit" value="Edit" /></td>
+    <td><al-if expr="bulletin.detail"><div class="preview"><al-value expr="wiki_text(bulletin.detail)" noescape="noescape" /></div></al-if></td>
+   </tr>
+   <tr>
+    <td colspan="2" class="darker">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="center" width="100%">
+         <al-input class="butt danger" name="delete" type="submit" value="delete" />
+        </td>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   <tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_bulletin.py b/pages/admin_bulletin.py
new file mode 100644
index 0000000..2518fdb
--- /dev/null
+++ b/pages/admin_bulletin.py
@@ -0,0 +1,102 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, datetime
+from cocklebur.pt import SelectPT
+from casemgr import globals
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.group_edit.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.bulletin.db_has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        def normalise_date(msg, dt):
+            if dt is not None:
+                try:
+                    return str(datetime.mx_parse_datetime(dt))
+                except datetime.Error, e:
+                    raise page_common.PageError('%s: "%s": %s' % (msg, dt, e))
+        bull = ctx.locals.bulletin
+        bull.post_date = normalise_date('Post date', bull.post_date)
+        bull.expiry_date = normalise_date('Expiry date', bull.expiry_date)
+        ctx.admin_log(bull.db_desc())
+        bull.db_update()
+        ctx.locals.group_edit.set_key(bull.bulletin_id)
+        ctx.admin_log(ctx.locals.group_edit.db_desc())
+        ctx.locals.group_edit.db_update()
+        globals.db.commit()
+        ctx.add_message('Updated bulletin %r' % bull.title)
+
+    def revert(self, ctx):
+        ctx.locals.group_edit.db_revert()
+        ctx.locals.bulletin.db_revert()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        if not ctx.locals.bulletin.is_new():
+            if not self.confirmed:
+                raise page_common.ConfirmDelete
+            ctx.locals.bulletin.db_delete()
+            globals.db.commit()
+        ctx.add_message('Deleted bulletin %r' % ctx.locals.bulletin.title)
+        ctx.pop_page()
+
+    def do_group_edit(self, ctx, op, *args):
+        ctx.locals.group_edit.do(op, *args)
+
+    def do_wikiedit(self, ctx, field):
+        prompt = {
+            'detail': 'Detail',
+        }[field]
+        ctx.push_page('admin_wikiedit', ctx.locals.bulletin, field, prompt,
+                      'Bulletin ' + ctx.locals.bulletin.title, 
+                      'admin-bulletins')
+    
+
+pageops = PageOps()
+
+
+def page_enter(ctx, bulletin_id):
+    if bulletin_id is None:
+        ctx.locals.bulletin = globals.db.new_row('bulletins')
+    else:
+        query = globals.db.query('bulletins')
+        query.where('bulletin_id = %s', bulletin_id)
+        ctx.locals.bulletin = query.fetchone()
+    ctx.locals.group_pt = globals.db.ptset('group_bulletins', 'bulletin_id', 
+                                           'group_id', bulletin_id)
+    ctx.locals.group_pt.get_slave_cache().preload_all()
+    ctx.locals.group_edit = SelectPT(ctx.locals.group_pt, 'group_name')
+    ctx.add_session_vars('bulletin', 'group_pt', 'group_edit')
+
+def page_leave(ctx):
+    ctx.del_session_vars('bulletin', 'group_pt', 'group_edit')
+
+def page_display(ctx):
+    ctx.run_template('admin_bulletin.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_bulletins.html b/pages/admin_bulletins.html
new file mode 100644
index 0000000..bea3dad
--- /dev/null
+++ b/pages/admin_bulletins.html
@@ -0,0 +1,81 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Bulletins</al-setarg>
+  <table class="gridtab" border="0">
+   <thead class="darker">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="5"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+       <al-input name="add_bulletin" 
+        class="smallbutt" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot class="darker">
+     <tr><td colspan="5"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="bulletins_i" expr="paged_search.result_page()">
+     <al-exec expr="bulletin = bulletins_i.value()" />
+     <al-if expr="bulletins_i.index() & 1">
+      <tr class="darker">
+     <al-else>
+      <tr>
+     </al-if>
+      <td><al-value expr="bulletin.title" /></td>
+      <td nowrap>
+       <al-if expr="bulletin.post_date">
+        <al-value expr="bulletin.post_date" />
+       <al-else>
+        none
+       </al-if>
+      </td>
+      <td nowrap>
+       <al-if expr="bulletin.expiry_date">
+        <al-value expr="bulletin.expiry_date" />
+       <al-else>
+        never
+       </al-if>
+      </td>
+      <td class="smaller"><al-value expr="bulletin.synopsis" /></td>
+      <td align="center" nowrap>
+       <al-input nameexpr="'edit_bulletin:%s' % bulletin.bulletin_id" 
+            class="smallbutt" type="submit" value="Edit" />
+      </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="5" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_bulletins.py b/pages/admin_bulletins.py
new file mode 100644
index 0000000..3e98f70
--- /dev/null
+++ b/pages/admin_bulletins.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add_bulletin(self, ctx, ignore):
+        ctx.locals.bulletin_result.reset()
+        ctx.push_page('admin_bulletin', None)
+
+    def do_edit_bulletin(self, ctx, bulletin_id):
+        ctx.locals.bulletin_result.reset()
+        ctx.push_page('admin_bulletin', int(bulletin_id))
+
+
+pageops = PageOps()
+
+def page_enter(ctx, bulletin_result):
+    bulletin_result.headers = [
+        ('title', 'Title'),
+        ('post_date', 'Posted'),
+        ('expiry_date', 'Expires'),
+        ('synopsis', 'Synopsis'),
+    ]
+    ctx.locals.bulletin_result = bulletin_result
+    paged_search.push_pager(ctx, ctx.locals.bulletin_result)
+    ctx.add_session_vars('bulletin_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('bulletin_result')
+ 
+def page_display(ctx):
+#    bulletin_ids = [pkey[0] for pkey in ctx.locals.bulletin_result.page_pkeys()]
+#    ctx.locals.groups_pt = globals.db.participation_table('group_bulletins',
+#                                                      'bulletin_id', 'group_id')
+#    ctx.locals.groups_pt.preload(bulletin_ids)
+    ctx.run_template('admin_bulletins.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_contact_type.html b/pages/admin_contact_type.html
new file mode 100644
index 0000000..01a975a
--- /dev/null
+++ b/pages/admin_contact_type.html
@@ -0,0 +1,57 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.contact_label" /> type: <al-value expr="contact_type.name" /></al-setarg>
+ <al-expand name="confirm_or_error">
+  <table class="widelabelform">
+   <tr>
+    <th colspan="3">
+     <al-value expr="contact_type.name" />
+     <span class="smaller">
+       - <al-value expr="contact_type.count" /> association(s)
+     </span>
+    </th>
+   </tr>
+   <tr>
+    <td class="label">Rename to</td>
+    <td class="field"><al-input type="text" name="contact_type.new_name" /></td>
+    <td>
+     <al-input type="submit" name="rename" class="butt" value="Rename" /></td>
+   </tr>
+   <al-if expr="contact_type.count">
+    <tr>
+     <td class="label">Merge into</td>
+     <td class="field">
+       <al-select name="contact_type.merge_to_id" 
+          optionexpr="contact_type.merge_types()" /></td>
+     <td>
+      <al-input type="submit" name="merge" class="butt" value="Merge" /></td>
+    </tr>
+   </al-if>
+    <tr>
+     <td class="label">Delete</td>
+     <td></td>
+     <td>
+      <al-input type="submit" name="delete" class="danger butt" value="Delete" /></td>
+    </tr>
+  </table>
+ </al-expand>
+</al-expand>
diff --git a/pages/admin_contact_type.py b/pages/admin_contact_type.py
new file mode 100644
index 0000000..49b0fdb
--- /dev/null
+++ b/pages/admin_contact_type.py
@@ -0,0 +1,101 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import utils, dbobj
+from casemgr import globals, contacts
+
+from pages import page_common
+
+import config
+
+class MergeConfirm(page_common.Confirm):
+    title = 'Merge contact types'
+    buttons = [
+        ('continue', 'No'),
+        ('confirm', 'Yes, Continue'),
+    ]
+
+
+class DeleteConfirm(page_common.Confirm):
+    title = 'Delete contact type'
+    buttons = [
+        ('continue', 'No'),
+        ('confirm', 'Yes, Continue'),
+    ]
+
+
+class PageOps(page_common.PageOpsBase):
+    
+    def do_rename(self, ctx, ignore):
+        if ctx.locals.contact_type.name != ctx.locals.contact_type.new_name:
+            try:
+                ctx.locals.contact_type.rename()
+            except dbobj.DuplicateKeyError:
+                raise dbobj.DuplicateKeyError('Name is already used')
+            globals.db.commit()
+            ctx.msg('info', 'Renamed contact type %r to %r' % 
+                        (ctx.locals.contact_type.name, 
+                        ctx.locals.contact_type.new_name))
+            ctx.pop_page()
+
+    def do_merge(self, ctx, ignore):
+        ct = ctx.locals.contact_type
+        if not ct.merge_to_id:
+            return
+        if not self.confirmed:
+            raise MergeConfirm(message='This will merge %d contact(s) of type '
+                               '%r into %r. This cannot be undone. Are you '
+                               'sure you wish to proceed?' % 
+                             (ct.count, ct.name, ct.type_label(ct.merge_to_id)))
+        ct.merge()
+        ctx.msg('info', 'Merged %d contact(s) of type %r to %r' % 
+                        (ct.count, ct.name, ct.type_label(ct.merge_to_id)))
+                     
+        globals.db.commit()
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        ct = ctx.locals.contact_type
+        if not self.confirmed:
+            raise DeleteConfirm(message='This will delete %d contact(s) of '
+                                'type %r. This cannot be undone. Are you '
+                                'sure you wish to proceed?' % 
+                                (ct.count, ct.name))
+        ct.delete()
+        ctx.msg('info', 'Deleted %d contact(s) of type %r' % 
+                        (ct.count, ct.name))
+        globals.db.commit()
+        ctx.pop_page()
+
+
+pageops = PageOps()
+
+
+def page_enter(ctx, contact_type_id):
+    ctx.locals.contact_type = contacts.ContactTypeEdit(contact_type_id)
+    ctx.add_session_vars('contact_type')
+
+def page_leave(ctx):
+    ctx.del_session_vars('contact_type')
+
+def page_display(ctx):
+    ctx.locals.contact_type.refresh()
+    ctx.run_template('admin_contact_type.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_contact_types.html b/pages/admin_contact_types.html
new file mode 100644
index 0000000..b7b8940
--- /dev/null
+++ b/pages/admin_contact_types.html
@@ -0,0 +1,48 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.contact_label" /> Types</al-setarg>
+ <table class="gridtab">
+  <thead>
+   <tr>
+    <th><al-value expr="config.contact_label" /> Type</th>
+    <th>Associations</th>
+    <th></th>
+   </tr>
+  </thead>
+  <tbody>
+   <al-for vars="ct" expr="contact_types">
+    <tr>
+     <td><al-value expr="ct.contact_type" /></td>
+     <td align="right"><al-value expr="ct.count" /></td>
+     <td><al-input type="submit" class="smallbutt" value="Edit"
+           nameexpr="'edit:%s' % ct.contact_type_id" /></td>
+    </tr>
+   </al-for>
+   <tr>
+    <td colspan="2">
+     <al-input type="text" style="width: 99%;" name="new_contact_type" />
+    </td>
+    <td><al-input type="submit" class="smallbutt" value="New" name="new" /></td>
+   </tr>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/admin_contact_types.py b/pages/admin_contact_types.py
new file mode 100644
index 0000000..dc55cca
--- /dev/null
+++ b/pages/admin_contact_types.py
@@ -0,0 +1,52 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import utils
+from casemgr import globals, contacts
+
+from pages import page_common
+
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    
+    def do_new(self, ctx, ignore):
+        if ctx.locals.new_contact_type:
+            contacts.new_contact_type(ctx.locals.new_contact_type.strip())
+            globals.db.commit()
+        ctx.locals.new_contact_type = None
+
+    def do_edit(self, ctx, contact_type_id):
+        ctx.push_page('admin_contact_type', int(contact_type_id))
+
+pageops = PageOps()
+
+
+def page_enter(ctx):
+    pass
+
+def page_leave(ctx):
+    pass
+
+def page_display(ctx):
+    ctx.locals.contact_types = contacts.ContactTypesAdmin()
+    ctx.run_template('admin_contact_types.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_form_deploy.html b/pages/admin_form_deploy.html
new file mode 100644
index 0000000..03d4d2f
--- /dev/null
+++ b/pages/admin_form_deploy.html
@@ -0,0 +1,77 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Form schema roll-forward plan - 
+  <al-value expr="form_meta.name"> - 
+  <al-value expr="form_meta.root.text"></al-setarg>
+
+  <table class="f-deploy">
+   <tr>
+    <th colspan="3">Current</th>
+    <th></th>
+    <th colspan="3">New</th>
+   </tr>
+   <al-for iter="diff_i" expr="diffs">
+    <al-exec expr="col = diff_i.value()" />
+    <al-tr classexpr="col.style">
+     <al-if expr="col.col_a is not None">
+      <td><al-value expr="col.col_a.name" /></td>
+      <td><al-value expr="col.col_a_sql" /></td>
+      <td align="center"><al-input type="radio" name="old_select"
+       valueexpr="col.col_a.name" /></td>
+     <al-else>
+      <td></td><td></td><td></td>
+     </al-if>
+     <td class="op"><al-value expr="col.op" /></td>
+     <al-if expr="col.col_b is not None">
+      <td align="center"><al-input type="radio" name="new_select"
+       valueexpr="col.col_b.name" /></td>
+      <td><al-value expr="col.col_b.name" /></td>
+      <td><al-value expr="col.col_b_sql" /></td>
+     <al-else>
+      <td></td><td></td><td></td>
+     </al-if>
+    </al-tr>
+   </al-for>
+   <tr>
+    <td colspan="7">
+     <al-expand name="confirm_or_error">
+      <table width="100%">
+       <tr>
+        <td align="center">
+         <al-input class="bigbutt" style="width: 100%;" type="submit" 
+          name="rename" value="Join selected pair" /><br>
+         <al-input class="bigbutt" style="width: 100%;" type="submit" 
+          name="recreate" value="Split selected pair" />
+        </td>
+        <td align="right">
+         <al-if expr="diffs.okay()">
+          <al-input class="butt" type="submit" name="deploy" value="Deploy" />
+         </al-if>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   </tr>
+  </table>
+
+</al-expand>
diff --git a/pages/admin_form_deploy.py b/pages/admin_form_deploy.py
new file mode 100644
index 0000000..1697ddb
--- /dev/null
+++ b/pages/admin_form_deploy.py
@@ -0,0 +1,112 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import difflib
+import itertools
+
+from cocklebur import dbobj
+
+from casemgr import globals
+
+from casemgr.admin import formmeta, tablediff
+
+from pages import page_common
+
+import config
+
+class ConfirmSave(page_common.ConfirmSave):
+    message = 'You have made changes to this form that have not been saved yet'
+    buttons = [
+        ('continue', 'Continue Editing'),
+        ('savefirst', 'Save First'),
+    ]
+
+null_rollforward_msg = ('No fields are to be rolled forward! Either create a '
+                        'new form, or roll forward some fields.')
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.form_meta.has_changed():
+            raise ConfirmSave
+
+    def commit(self, ctx):
+        assert ctx.locals.form_meta.name
+        ctx.locals.form_meta.save(globals.db)
+
+    def do_back(self, ctx, ignore):
+        # Don't want the standard do_back's check_unsaved_or_confirmed()
+        ctx.pop_page()
+
+    def do_deploy(self, ctx, ignore):
+        if not ctx.locals.diffs.okay():
+            raise page_common.PageError('Resolve column incompatibilities first')
+        if ctx.locals.diffs.is_drop_all():
+            raise page_common.PageError(null_rollforward_msg)
+        rollforward_map = ctx.locals.diffs.rollforward_map()
+        self.check_unsaved_or_confirmed(ctx)
+        if ctx.locals.form_meta.has_changed():
+            ctx.locals.form_meta.save(globals.db)
+        ctx.locals.form_meta.deploy(globals.db, rollforward_map)
+        ctx.pop_page()
+
+    def do_rename(self, ctx, ignore):
+        try:
+            ctx.locals.diffs.rename(ctx.locals.old_select, 
+                                    ctx.locals.new_select)
+        except tablediff.TableDiffError, e:
+            raise page_common.PageError(str(e))
+
+    def do_recreate(self, ctx, ignore):
+        try:
+            ctx.locals.diffs.recreate(ctx.locals.old_select,
+                                      ctx.locals.new_select)
+        except tablediff.TableDiffError, e:
+            raise page_common.PageError(str(e))
+
+pageops = PageOps()
+
+
+def page_enter(ctx):
+    deployed_table = globals.formlib.tablename(ctx.locals.form_meta.name, 
+                                           ctx.locals.form_meta.vers_deployed())
+    try:
+        table_desc_a = globals.db.get_table(deployed_table)
+    except KeyError:
+        table_desc_a = None
+    form = ctx.locals.form_meta.to_form()
+    form.update_columns()
+    table_desc_b = dbobj.table_describer.TableDescriber(None, 'xx')
+    for column in form.columns:
+        table_desc_b.column(column.name, column.type, **column.kwargs)
+    ctx.locals.diffs = tablediff.describe_changes(table_desc_a, table_desc_b)
+    ctx.locals.old_select = ctx.locals.new_select = None
+    ctx.add_session_vars('diffs', 'old_select', 'new_select')
+
+def page_leave(ctx):
+    ctx.del_session_vars('diffs', 'old_select', 'new_select')
+
+def page_display(ctx):
+    if ctx.locals.diffs.is_drop_all() and not ctx.req_equals('deploy'):
+        ctx.msg('warn', null_rollforward_msg)
+    ctx.run_template('admin_form_deploy.html')
+
+def page_process(ctx):
+    try:
+        if pageops.page_process(ctx):
+            return
+    except formmeta.Errors, e:
+        ctx.add_error(e)
diff --git a/pages/admin_form_diff.html b/pages/admin_form_diff.html
new file mode 100644
index 0000000..f0ee660
--- /dev/null
+++ b/pages/admin_form_diff.html
@@ -0,0 +1,37 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Form Diff</al-setarg>
+ <al-setarg name="extrastyle">
+   .diff { margin: 0 2ex 0 2ex; }
+   .diff tbody {
+    border-top: 4px solid #ccc;
+    border-bottom: 4px solid #ccc;}
+   .diff_header {background-color:#e0e0e0}
+   td.diff_header {text-align:right}
+   .diff_next {display: none; background-color:#c0c0c0}
+   .diff_add {background-color:#aaffaa}
+   .diff_chg {background-color:#ffff77}
+   .diff_sub {background-color:#ffaaaa}
+ </al-setarg>
+ <al-value expr="difftable" noescape />
+</al-expand>
+
diff --git a/pages/admin_form_diff.py b/pages/admin_form_diff.py
new file mode 100644
index 0000000..84c1ea0
--- /dev/null
+++ b/pages/admin_form_diff.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+try:
+    import difflib23 as difflib
+except ImportError:
+    import difflib
+from cStringIO import StringIO
+
+from cocklebur.form_ui.xmlsave import xmlsave
+from casemgr import globals
+from pages import page_common
+
+import config
+
+def form_xml(form):
+    f = StringIO()
+    xmlsave(f, form)
+    return f.getvalue().splitlines()
+
+class PageOps(page_common.PageOpsBase):
+    pass
+
+page_process = PageOps().page_process
+
+def page_enter(ctx, from_form, to_form, from_label, to_label):
+    from_xml = form_xml(from_form)
+    to_xml = form_xml(to_form)
+    differ = difflib.HtmlDiff(wrapcolumn=80)
+    ctx.locals.difftable = differ.make_table(from_xml, to_xml, 
+                                    from_label, to_label, context=True)
+    ctx.add_session_vars('difftable')
+
+def page_leave(ctx):
+    ctx.del_session_vars('difftable')
+
+def page_display(ctx):
+    ctx.run_template('admin_form_diff.html')
+
diff --git a/pages/admin_form_edit.html b/pages/admin_form_edit.html
new file mode 100644
index 0000000..5f16dfc
--- /dev/null
+++ b/pages/admin_form_edit.html
@@ -0,0 +1,233 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="form_inputs.html" />
+
+<al-macro name="formedit">
+     <table class="admin-fe-work" id="formedit" border="0">
+      <al-tree iter="tree_i" expr="form_meta.root">
+       <al-exec expr="node = tree_i.value()" />
+
+       <al-if expr="node != form_meta.root">
+        <al-comment><!-- Add section/question --></al-comment>
+        <tr>
+         <al-if expr="tree_i.depth() > 1">
+          <al-td colspanexpr="tree_i.depth() - 1" class="line"></al-td>
+         </al-if>
+         <al-td colspanexpr="tree_i.span() + 2" class="line hover-expand">
+          <al-input type="image" alt="add" class="he-opener"
+                srcexpr="appath('images/plus.png')"
+                nameexpr="'add:%s' % node.id()" />
+          <span class="he-open">
+           <al-input type="image" alt="add question"
+                 srcexpr="appath('images/add-question.png')"
+                 nameexpr="'add_question:%s' % node.id()" />
+           <al-input type="image" alt="add section"
+                 srcexpr="appath('images/add-section.png')"
+                 nameexpr="'add_section:%s' % node.id()" />
+           <al-if expr="form_cutbuff">
+            <al-input type="image" alt="paste"
+                  srcexpr="appath('images/add-paste.png')"
+                  nameexpr="'paste:%s' % node.id()" />
+           </al-if>
+          </span>
+         </al-td>
+        </tr>
+       </al-if>
+
+       <al-if expr="node.node_type != 'end'">
+        <al-tr classexpr="node.css_class()" idexpr="node.id()">
+         <al-comment><!-- Q number --></al-comment>
+         <al-if expr="tree_i.depth()">
+          <al-if expr="tree_i.depth() > 1">
+           <al-td colspanexpr="tree_i.depth() - 1"></al-td>
+          </al-if>
+          <td class="number"><al-value expr="node.get_label()" /></td>
+         </al-if>
+         <al-comment><!-- Text --></al-comment>
+         <al-if expr="node.node_type == 'section'">
+          <al-td colspanexpr="tree_i.span() + 1" class="qtext">
+           <al-if expr="section_edit and section_edit.node == node">
+            <div class="noselect fieldandbutt">
+             <div class="andbutt">
+              <al-input type="submit" class="butt" 
+               name="section_edit_ok" value="Okay" />
+              <al-input type="submit" class="butt" 
+               name="section_edit_cancel" value="Cancel" />
+              <al-expand name="wikihelp" />
+             </div>
+             <div class="fieldand">
+             <al-textarea cols="40" rows="4" 
+              name="section_edit.text" noescape />
+             </div>
+            </div>
+           <al-else>
+            <al-value expr="wiki_text(node.text)" noescape />
+           </al-if>
+          </al-td>
+         <al-elif expr="node.node_type == 'question'">
+          <al-td colspanexpr="tree_i.span() + (not node.inputs)" class="qtext">
+           <al-for iter="skiptext_i" expr="node.skiptext()">
+            <div class="skiptext"><al-value expr="wiki_oneliner(skiptext_i.value())" noescape></div>
+           </al-for>
+           <al-if expr="getattr(node, 'help', None)">
+            <al-img expr="appath('images/info.png')" class="right" />
+           </al-if>
+           <al-value expr="wiki_text(node.text)" noescape />
+          </al-td>
+          <al-comment><!-- Inputs --></al-comment>
+          <al-if expr="node.inputs">
+           <td class="inputs">
+            <table width="100%" border="0">
+             <al-for iter="input_i" expr="node.inputs">
+              <al-exec expr="input = input_i.value()" />
+              <al-value lookup="input_markup" expr="input.render" />
+              <al-if expr="input_i.index() < len(node.inputs) - 1">
+               <tr><td class="line" colspan="2"></td></tr>
+              </al-if>
+             </al-for>
+            </table>
+           </td>
+          </al-if>
+         </al-if>
+        </al-tr>
+       </al-if>
+      </al-tree>
+     </table>
+     <al-input type="hidden" name="selected" />
+     <script>
+      jsselect('formedit', fe_select('appform', 'selected', 
+                              ['Cancel', 'Copy', 'Cut']), fe_click, true);
+      attachExpandOnHover('formedit');
+      scrollRemember('appform', 'fe_<al-value expr="form_meta.name"/>');
+     </script>
+</al-macro>
+
+<al-expand name="page_layout_admin">
+ <al-script srcexpr="appath('admin.js')" type="text/javascript"></al-script>
+ <al-setarg name="title">Design form - 
+  <al-value expr="form_meta.name"> - 
+  <al-value expr="form_meta.root.text"></al-setarg>
+
+  <al-expand name="confirm_or_error" />
+
+  <al-if expr="mode == 'rename' or mode.startswith('saveas')">
+   <div class="centbox">
+    <h1><al-if expr="mode == 'rename'">Rename<al-else>Save As</al-if></h1>
+    New name: <al-input size="30" type="text" name="new_name" />
+     <al-input class="butt" type="submit" name="name_okay" value="Okay" />
+     <al-input class="butt" type="submit" name="name_cancel" value="Cancel" />
+   </div>
+  </al-if>
+
+  <al-if expr="mode == 'import'">
+   <div class="centbox">
+    <h1>Import a form definition</h1>
+    Local file: <al-input class="file" type="file" name="import_form" />
+     <al-input class="butt" type="submit" name="name_okay" value="Okay" />
+     <al-input class="butt" type="submit" name="name_cancel" value="Cancel" />
+   </div>
+  </al-if>
+
+  <al-if expr="mode == 'delete_confirm'">
+   <div class="centbox">
+    <h1>Delete this form definition</h1>
+    <p>You have requested that this form definition be deleted. Note that this
+    operation cannot be undone - both the form definition and <b>all form data
+    will be lost</b>. Are you sure you want to delete this form?</p>
+    <al-input type="submit" name="delete_okay" value="Yes, Delete" />
+      <al-input type="submit" name="delete_cancel" value="Do not Delete" />
+   </div>
+  </al-if>
+
+  <al-if expr="mode == 'edit' and not confirm">
+   <table class="admin-fe">
+    <tr>
+     <td class="nav">
+      <al-input type="submit" class="item butt" name="preview"   value="Preview" />
+      <al-if expr="form_meta.has_changed() and form_meta.name">
+       <al-input type="submit" class="item butt" name="save"     value="Save" />
+      </al-if>
+      <al-input type="submit" class="item butt" name="saveas"  value="Save As" />
+      <al-input type="submit" class="item butt" name="rename"   value="Rename" />
+      <al-if expr="form_meta.name">
+       <al-input type="submit" class="item butt" name="export"   value="Export" />
+      </al-if>
+      <al-input type="submit" class="item butt" name="import"   value="Import" />
+      <br />
+      <table class="item">
+       <tr>
+        <td>Type:</td>
+        <td><al-select name="form_meta.root.form_type" onchange="submit();"
+         optionexpr="form_meta.root.form_types" /></td>
+       </tr>
+       <tr>
+        <td>Multi:</td>
+        <td><al-input type="checkbox" name="form_meta.root.allow_multiple" value="True" onclick="submit();" /></td>
+       </tr>
+      </table>
+      <br />
+      <table class="item smaller">
+       <tr><th colspan="2">Versions</th></tr>
+       <tr>
+        <td>This <al-if expr="form_meta.has_changed()">(modified)</al-if>:</td>
+        <td align="right"><al-value expr="form_meta.version" /></td>
+       </tr>
+       <al-if expr="form_meta.root.update_time">
+        <tr>
+         <td colspan="2"><al-value expr="form_meta.root.update_time" /></td>
+        </tr>
+       </al-if>
+       <tr>
+        <td>Deployed:</td>
+        <td align="right"><al-value expr="form_meta.vers_deployed()" /></td>
+       </tr>
+      </table>
+      <al-select name="vers_select" onchange="submit();" class="item"
+        optionexpr="form_meta.available_vers()" />
+      <al-input type="submit" class="item butt" name="vers_go" value="Change Vers" />
+      <al-input type="submit" class="item butt" name="diff" value="Diff vs deployed" />
+      <br />
+      <al-if expr="form_meta.version != form_meta.vers_deployed() or form_meta.has_changed()">
+       <al-input type="submit" class="item butt" name="deploy"   value="Deploy" />
+       <br />
+      </al-if>
+      <table width="100%" class="smaller">
+       <tr><th>Used by:</th></tr>
+       <al-if expr="used_by">
+        <al-for iter="i" expr="used_by">
+         <tr><td><al-value expr="i.value()" /></td></tr>
+        </al-for>
+       <al-else>
+        <tr><td>- none -</td></tr>
+       </al-if>
+      </table>
+      <al-if expr="not used_by">
+       <al-input type="submit" class="item butt danger" name="delete" value="Delete" />
+      </al-if>
+     </td>
+
+     <td>
+       <al-expand name="formedit" />
+     </td>
+    </tr>
+   </table>
+  </al-if>
+</al-expand>
diff --git a/pages/admin_form_edit.py b/pages/admin_form_edit.py
new file mode 100644
index 0000000..679155e
--- /dev/null
+++ b/pages/admin_form_edit.py
@@ -0,0 +1,243 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj, form_ui
+from cocklebur.form_ui.xmlsave import xmlsave
+from cocklebur.form_ui.xmlload import xmlload
+
+import config
+
+from casemgr import globals
+
+from casemgr.admin import formedit, formmeta
+
+from pages import page_common
+
+
+class DummyFormData: pass
+
+
+class SetMode(Exception): pass
+
+
+class ConfirmSave(page_common.ConfirmSave):
+    message = 'You have made changes to this form that have not been saved yet'
+
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.form_meta.has_changed():
+            raise ConfirmSave
+
+    def commit(self, ctx):
+        if not ctx.locals.form_meta.name:
+            if ctx.locals.confirm:
+                raise SetMode('saveas_' + ctx.locals.confirm.action)
+        ctx.locals.form_meta.save(globals.db)
+
+    def rollback(self, ctx):
+        if ctx.locals.form_meta.has_changed():
+            try:
+                ctx.locals.form_meta.load_version(ctx.locals.form_meta.version)
+            except form_ui.NoFormError:
+                pass
+
+    def do_preview(self, ctx, ignore):
+        ctx.push_page('admin_form_preview')
+
+    def do_deploy(self, ctx, ignore):
+        if config.form_rollforward:
+            if not ctx.locals.form_meta.name:
+                raise SetMode('saveas_deploy')
+            ctx.locals.mode = 'edit'
+            ctx.push_page('admin_form_deploy')
+        else:
+            self.check_unsaved_or_confirmed(ctx)
+            ctx.locals.form_meta.deploy(globals.db)
+
+    def do_vers(self, ctx, version):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.locals.form_meta.load_version(version)
+        ctx.locals.mode = 'edit'
+
+    def do_diff(self, ctx, ignore):
+        name = ctx.locals.form_meta.name
+        to_form = ctx.locals.form_meta.to_form()
+        query = globals.db.query('forms')
+        query.where('label = %s', name)
+        row = query.fetchone()
+        from_form = globals.formlib.load(name, row.cur_version)
+        from_label = 'Deployed (version %s)' % from_form.version
+        to_label = 'Current'
+        if ctx.locals.form_meta.version < row.cur_version:
+            from_form, to_form = to_form, from_form
+            from_label, to_label = to_label, from_label
+        ctx.push_page('admin_form_diff', from_form, to_form, 
+                                         from_label, to_label)
+
+    def do_rename(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.locals.new_name = ctx.locals.form_meta.name
+        ctx.locals.mode = 'rename'
+
+    def do_import(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.locals.mode = 'import'
+
+    def do_export(self, ctx, ignore):
+        form_meta = ctx.locals.form_meta
+        downloader = page_common.download(ctx, form_meta.name + '.xml',
+                                          content_type='text/xml')
+        xmlsave(downloader, form_meta.to_form())
+
+    def do_import_form(self, ctx, ignore):
+        if len(ctx.locals.import_form) != 1:
+            ctx.locals.mode = 'edit'
+            raise formmeta.Error('Upload only one file at a time')
+        formfile = ctx.locals.import_form[0]
+        try:
+            ctx.locals.form_meta.from_form(xmlload(formfile.file))
+        except formmeta.Errors, e:
+            ctx.add_error(e)
+        else:
+            ctx.locals.mode = 'edit'
+
+    def do_save(self, ctx, ignore):
+        ctx.locals.form_meta.save(globals.db)
+
+    def do_saveas(self, ctx, ignore):
+        ctx.locals.new_name = ctx.locals.form_meta.name
+        ctx.locals.mode = 'saveas'
+
+    def do_delete(self, ctx, ignore):
+        ctx.locals.mode = 'delete_confirm'
+
+    def do_delete_okay(self, ctx, ignore):
+        ctx.locals.form_meta.delete(globals.db)
+        ctx.pop_page()
+
+    def do_delete_cancel(self, ctx, ignore):
+        ctx.locals.mode = 'edit'
+
+    def do_name_okay(self, ctx, ignore):
+        # Save As/Rename - okay
+        try:
+            if ctx.locals.mode == 'rename':
+                ctx.locals.form_meta.rename(globals.db, ctx.locals.new_name)
+                ctx.locals.mode = 'edit'
+            elif ctx.locals.mode.startswith('saveas'):
+                ctx.locals.form_meta.save_as(globals.db, ctx.locals.new_name)
+                if ctx.locals.mode.startswith('saveas_'):
+                    self.dispatch(ctx, ctx.locals.mode[len('saveas_'):], [None])
+                else:
+                    ctx.locals.mode = 'edit'
+        except formmeta.Errors, e:
+            ctx.add_error(e)
+
+    def do_name_cancel(self, ctx, ignore):
+        # Save As/Rename - cancel
+        ctx.locals.mode = 'edit'
+
+    def do_add_question(self, ctx, path):
+        question = ctx.locals.form_meta.root.new_question(path)
+        ctx.push_page('admin_form_edit_question', question)
+
+    def do_add_section(self, ctx, path):
+        ctx.locals.section_edit = ctx.locals.form_meta.root.new_section(path)
+
+    def do_edit(self, ctx, path):
+        if ctx.locals.section_edit:
+            ctx.locals.section_edit.rollback()
+            ctx.locals.section_edit = None
+        root = ctx.locals.form_meta.root
+        if path[0] == 'Q':
+            ctx.push_page('admin_form_edit_question', root.edit_question(path))
+        elif path[0] == 'S':
+            ctx.locals.section_edit = root.edit_section(path)
+
+    def do_section_edit_ok(self, ctx, ignore):
+        ctx.locals.section_edit.commit()
+        ctx.locals.section_edit = None
+
+    def do_section_edit_cancel(self, ctx, ignore):
+        ctx.locals.section_edit.rollback()
+        ctx.locals.section_edit = None
+
+    def do_copy(self, ctx, ignore):
+        root = ctx.locals.form_meta.root
+        ctx.locals.form_cutbuff = root.copy(ctx.locals.selected)
+
+    def do_cut(self, ctx, ignore):
+        root = ctx.locals.form_meta.root
+        ctx.locals.form_cutbuff = root.cut(ctx.locals.selected)
+
+    def do_paste(self, ctx, path):
+        if ctx.locals.form_cutbuff:
+            ctx.locals.form_meta.root.paste(path, ctx.locals.form_cutbuff)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, name):
+    cred = ctx.locals._credentials
+    if name is None:
+        form_row = globals.db.new_row('forms')
+    else:
+        query = globals.db.query('forms')
+        query.where('label = %s', name)
+        form_row = query.fetchone()
+    try:
+        ctx.locals.form_meta = formmeta.FormMeta(form_row, cred)
+    except formmeta.Errors, e:
+        ctx.add_error(e)
+        ctx.pop_page()
+        return
+    ctx.add_session_vars('form_meta')
+    ctx.locals.section_edit = None
+    ctx.add_session_vars('section_edit')
+    ctx.locals.mode = 'edit'
+    ctx.add_session_vars('mode')
+    ctx.locals.vers_select = ''
+    ctx.add_session_vars('vers_select')
+    ctx.locals.form_data = DummyFormData()
+    ctx.add_session_vars('form_data')
+
+def page_leave(ctx):
+    ctx.del_session_vars('form_meta', 'section_edit', 'mode', 
+                         'vers_select', 'form_data')
+
+def page_display(ctx):
+    if not page_common.send_download(ctx):
+        form_meta = ctx.locals.form_meta
+        if ctx.locals.mode == 'edit':
+            ctx.locals.vers_select = form_meta.version
+        ctx.locals.used_by = form_meta.used_by(globals.db)
+        ctx.locals.form_disabled = True
+        ctx.run_template('admin_form_edit.html')
+
+def page_process(ctx):
+    try:
+        if pageops.page_process(ctx):
+            return
+        elif ctx.locals.form_meta.vers_changed(ctx.locals.vers_select):
+            pageops.dispatch(ctx, 'vers', (ctx.locals.vers_select,))
+    except SetMode, e:
+        ctx.locals.mode = e.args[0]
+    except formmeta.Errors, e:
+        ctx.add_error(e)
+        ctx.locals.mode = 'edit'
diff --git a/pages/admin_form_edit_input.html b/pages/admin_form_edit_input.html
new file mode 100644
index 0000000..29db401
--- /dev/null
+++ b/pages/admin_form_edit_input.html
@@ -0,0 +1,232 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="form_edit_input">
+      <table border="0" width="100%">
+       <al-exec expr="input = question.inputs[input_number]" />
+       <al-exec expr="name = 'question.inputs[%d]' % input_number" />
+
+       <tr>
+        <al-comment><!-- input type --></al-comment>
+        <td>
+         <al-select nameexpr="name + '.want_type'" onchange="submit();">
+          <al-for iter="g_i" expr="input.input_groups">
+           <al-if expr="isinstance(g_i.value()[1], list)">
+            <al-optgroup labelexpr="g_i.value()[0]">
+             <al-for iter="gi_i" expr="g_i.value()[1]">
+              <al-option labelexpr="gi_i.value()[1]" 
+                         valueexpr="gi_i.value()[0]" />
+             </al-for>
+            </al-optgroup>
+           <al-else>
+            <al-option labelexpr="g_i.value()[1]" 
+                       valueexpr="g_i.value()[0]" />
+           </al-if>
+          </al-for>
+         </al-select>
+        </td>
+       </tr>
+       <al-comment><!-- input options --></al-comment>
+       <tr>
+        <td colspan="1">
+         <table width="100%" border="0">
+          <al-if expr="input.error">
+           <tr>
+            <td colspan="5" bgcolor="orange" align="center">
+             <b><al-value expr="input.error" /></b>
+            </td>
+           </tr>
+          </al-if>
+          <tr>
+           <td class="prompt">Column name:</td>
+           <td colspan="4" width="100%">
+            <al-input type="text" size="30" nameexpr="name + '.column'" 
+             disabledbool="input.locked_column()" />
+           </td>
+          </tr>
+          <tr>
+           <td class="prompt">Input label:</td>
+           <td colspan="4" width="100%">
+            <al-input type="text" size="30" nameexpr="name + '.label'" />
+           </td>
+          </tr>
+          <tr>
+           <td class="prompt">Required:</td>
+           <td colspan="4" width="100%">
+            <al-input type="radio" nameexpr="name + '.required'" value="True" /> Yes
+            <al-input type="radio" nameexpr="name + '.required'" value="False" /> No
+           </td>
+          </tr>
+          <tr>
+           <td class="prompt">In summary?:</td>
+           <td colspan="4" width="100%">
+            <al-input type="radio" nameexpr="name + '.summarise'" value="True" /> Yes
+            <al-input type="radio" nameexpr="name + '.summarise'" value="False" /> No
+           </td>
+          </tr>
+          <al-for iter="fs" expr="input.fields">
+           <al-exec expr="f = fs.value()" />
+           <al-if expr="f.has(input)">
+            <tr>
+             <td class="prompt"><al-value expr="f.label" />:</td>
+             <td colspan="4" width="100%">
+              <al-input type="text" size="30" nameexpr="'%s.%s' % (name, f.name)" />
+             </td>
+            </tr>
+           </al-if>
+          </al-for>
+
+          <al-exec expr="options = input.default.get_options()" />
+          <al-for iter="default_i" expr="options">
+           <tr>
+            <al-if expr="default_i.index() == 0">
+             <al-td class="prompt" rowspanexpr="len(options)">
+              Default value:
+             </al-td>
+            </al-if>
+            <td colspan="3" bgcolor="#ffbbcc">
+             <al-input type="radio" nameexpr="name + '.default.type'" 
+              valueexpr="default_i.value()[0]" /> 
+             <al-value expr="default_i.value()[1]" />
+             <al-if expr="default_i.value()[0] == 'value'">
+              : <al-input type="text" nameexpr="name + '.default.value'" />
+             </al-if>
+            </td>
+           </tr>
+          </al-for>
+
+          <al-if expr="input.has_choices()">
+           <al-comment><!-- input options - choices --></al-comment>
+           <tr>
+            <td class="prompt">Choices:</td>
+            <td colspan="3">
+             <al-input type="hidden" nameexpr="name + '.choice_order'" />
+             <al-table idexpr="'choices%s' % input_number">
+              <tr>
+               <td width="24" align="right" valign="middle">
+                 <al-input type="image" alt="Add"
+                     srcexpr="appath('images/add-s.png')"
+                     nameexpr="'op:choice:add:%s' % input_number" />
+               </td>
+               <td class="choice">Key</td>
+               <td class="choice" width="100%">Label</td>
+              </tr>
+              <al-for iter="choice_i" expr="input.edit_choices">
+               <al-exec expr="choice_name = '%s:%s' % (input_number, choice_i.index())" />
+               <al-tr idexpr="'choice:' + choice_name">
+                <td><button class="move-handle">◈</button></td>
+                <td class="choice">
+                 <al-input type="text" size="12" 
+                  nameexpr="name + '.edit_choices[%d].key' % choice_i.index()" />
+                </td>
+                <td class="choice">
+                 <al-input type="text" style="width: 16em;"
+                  nameexpr="name + '.edit_choices[%d].label' % choice_i.index()" />
+                </td>
+                <td width="24" align="right">
+                  <al-input type="image" alt="Delete"
+                      srcexpr="appath('images/button-del.png')"
+                      nameexpr="'op:choice:del:' + choice_name" />
+                </td>
+               </al-tr>
+              </al-for>
+             </al-table>
+             <script>
+              rowMover('<al-value expr="'choices%s' % input_number" />',
+                       '<al-value expr="name + '.choice_order'" />');
+             </script>
+            </td>
+           </tr>
+
+           <al-if expr="input.has_direction()">
+            <tr>
+             <td class="prompt">Horizontal:</td>
+             <td colspan="4" width="100%">
+              <al-input type="radio" value="horizontal" onchange="submit();"
+                 nameexpr="name + '.direction'" />
+                Yes  
+              <al-input type="radio" value="vertical" onchange="submit();"
+                 nameexpr="name + '.direction'" />
+                No  
+              <al-input type="radio" value="auto" onchange="submit();"
+                 nameexpr="name + '.direction'" />
+                Auto
+             </td>
+            </tr>
+           </al-if>
+           <al-if expr="input.has_choices()">
+            <tr>
+             <td class="prompt">Skips:</td>
+             <td colspan="4" width="100%">
+              <table width="100%">
+               <tr>
+                <td width="24" align="right" valign="middle">
+                  <al-input type="image" alt="Add"
+                      srcexpr="appath('images/add-s.png')"
+                      nameexpr="'op:cond:add:%s' % input_number" />
+                </td>
+                <td class="choice">Name</td>
+                <td class="choice">Choice Keys</td>
+               </tr>
+               <al-for iter="skip_i" expr="input.skips">
+                <al-exec expr="skip = skip_i.value()" />
+                <al-exec expr="skip_name = '%s.skips[%s]' % (name, skip_i.index())" />
+                <tr>
+                 <td class="choice"> </td>
+                 <td>
+                  <al-input type="text" nameexpr="skip_name + '.name'" /><br>
+                  <al-input type="radio" value=""
+                     nameexpr="skip_name + '.not_selected'" />
+                    trigger if selected<br>
+                  <al-input type="radio" value="True"
+                     nameexpr="skip_name + '.not_selected'" />
+                    trigger if NOT selected<br>
+                  <al-input type="checkbox" value="True"
+                     nameexpr="skip_name + '.skip_remaining'" />
+                    skip remaining inputs<br>
+                  <al-input type="checkbox" value="True"
+                     nameexpr="skip_name + '.show_msg'" />
+                    show message<br>
+                 </td>
+                 <td>
+                  <al-for iter="skipvalue_i" expr="input.choicevalues()">
+                   <al-input type="checkbox" nameexpr="skip_name + '.values'"
+                     valueexpr="skipvalue_i.value()" list />
+                       <al-value expr="skipvalue_i.value()" /><br>
+                  </al-for>
+                 </td>
+                 <td width="24" align="right">
+                   <al-input type="image" alt="Delete"
+                       srcexpr="appath('images/button-del.png')"
+                       nameexpr="'op:cond:del:%s:%s' % (input_number, skip_i.index())" />
+                 </td>
+                </tr>
+               </al-for>
+              </table>
+             </td>
+            </tr>
+           </al-if>
+          </al-if>
+
+         </table>
+        </td>
+       </tr>
+      </table>
+</al-macro>
diff --git a/pages/admin_form_edit_question.html b/pages/admin_form_edit_question.html
new file mode 100644
index 0000000..ad6d444
--- /dev/null
+++ b/pages/admin_form_edit_question.html
@@ -0,0 +1,193 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="form_inputs.html" />
+<al-include name="admin_form_edit_input.html" />
+
+<al-macro name="feq_add">
+      <al-input type="image" alt="add" class="he-opener"
+                srcexpr="appath('images/plus.png')"
+                nameexpr="'add:%s' % index" />
+       <span class="he-open">
+        <al-input type="image" alt="add"
+          srcexpr="appath('images/add-input.png')"
+          nameexpr="'op:input:add:%s' % index" />
+        <al-if expr="feq_cutbuff">
+         <al-input type="image" alt="paste"
+           srcexpr="appath('images/add-paste.png')"
+           nameexpr="'paste:%s' % index" />
+        </al-if>
+       </span>
+</al-macro>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Design form question - section "<al-value expr="question.edit_info.section_title" />"</al-setarg>
+ <al-script srcexpr="appath('admin.js')" type="text/javascript"></al-script>
+ <table class="admin-fe-work" cellpadding="0" id="feq_edit">
+  <tr>
+   <td class="fe-left">
+    <table width="100%" border="0" cellspacing="0" cellpadding="0">
+
+     <al-comment><!-- Question text --></al-comment>
+     <tr id="select:question">
+      <td>
+       <b>Question:</b><br>
+       <al-if expr="getattr(question, 'help', None)">
+        <al-img expr="appath('images/info.png')" class="right" />
+       </al-if>
+       <al-value expr="wiki_text(question.text)" noescape />
+      </td>
+     </tr>
+
+     <al-for iter="input_i" expr="question.inputs">
+      <al-comment><!-- Add input row --></al-comment>
+      <tr>
+       <td class="line hover-expand">
+        <al-exec expr="index = input_i.index()" />
+        <al-expand name="feq_add" />
+       </td>
+      </tr>
+
+      <al-tr class="selectable" idexpr="'select:input_%d' % input_i.index()">
+       <al-comment><!-- Input preview --></al-comment>
+       <td class="inputview">
+        <table width="100%" border="0">
+         <al-exec expr="input = input_i.value()" />
+         <al-value lookup="input_markup" expr="input.get_renderer()" />
+        </table>
+       </td>
+      </al-tr>
+     </al-for>
+
+     <al-comment><!-- final add input --></al-comment>
+     <tr>
+      <td class="line hover-expand">
+       <al-exec expr="index = len(question.inputs)" />
+       <al-expand name="feq_add" />
+      </td>
+     </tr>
+
+     <al-comment><!-- Skips --></al-comment>
+     <tr id="select:triggers">
+      <td>
+       <al-if expr="question.disabled">
+        <div class="reverr">Question is disabled</div>
+       <al-else>
+        <b>Conditional:</b> <al-value expr="question.describe_triggers()" />
+       </al-if>
+      </td>
+     </tr>
+
+     <tr><td width="100%" class="line"></td></tr>
+
+    </table>
+   </td>
+
+   <td class="fe-divide active"> </td>
+
+   <td class="fe-right active">
+
+    <div id="fold:question">
+     <b><div class="right"><al-expand name="wikihelp" /></div>Question text:</b><br>
+     <al-textarea name="question.text" rows="10" /><br>
+     <b><div class="right"><al-expand name="wikihelp" /></div>Help text:</b><br>
+     <al-textarea name="question.help" rows="10" /><br>
+     <b>Help preview:</b><al-input type="submit" class="butt" name="preview" value="Refresh" /><br>
+     <div class="preview">
+      <al-value expr="wiki_text(question.help)" noescape />
+     </div>
+    </div>
+
+    <div id="fold:triggers">
+     <b>Skips and enables</b><br>
+     Unconditionally disable question? 
+      <al-input type="radio" name="question.disabled" value="True" /> Yes
+      <al-input type="radio" name="question.disabled" value="" /> No
+      <br>
+     Conditions 
+      <al-input type="radio" name="question.trigger_mode" value="enable" /> Enable
+      <al-input type="radio" name="question.trigger_mode" value="disable" /> Disable
+      question?<br>
+     <al-if expr="question.edit_info.other_condition_names">
+      <table border="0" width="100%">
+       <tr>
+        <td width="24" align="right" valign="middle">
+          <al-input type="image" alt="Add"
+              srcexpr="appath('images/add-s.png')"
+              name="op:trigger:add" /></td>
+        <th class="choice">Triggers</th>
+        <th></th>
+       </tr>
+       <al-for iter="trigger_i" expr="question.triggers">
+        <al-exec expr="trigger = trigger_i.value()" />
+        <al-exec expr="triggername = 'question.triggers[%d]' % trigger_i.index()" />
+        <tr>
+         <td></td>
+         <td class="choice">
+          <al-select nameexpr="triggername + '.name'" style="width: 100%;"
+           optionexpr="question.edit_info.sorted_condition_names()" />
+         </td>
+         <td width="24" align="right">
+          <al-input type="image" alt="Delete"
+              srcexpr="appath('images/button-del.png')"
+              nameexpr="'op:trigger:del:%s' % trigger_i.index()" /></td>
+        </tr>
+       </al-for>
+      </table>
+     <al-else>
+      No triggers available for this question.
+     </al-if>
+    </div>
+
+    <al-for vars="input_number" expr="range(len(question.inputs))">
+     <al-div idexpr="'fold:input_%d' % input_number">
+      <al-expand name="form_edit_input" />
+     </al-div>
+    </al-for>
+
+   </td>
+  </tr>
+  <tr>
+   <td valign="top" align="center">
+    <al-expand name="confirm_or_error">
+     <table width="100%">
+      <tr>
+       <td align="left">
+        <al-input type="submit" class="butt" name="cancel" value="<< Cancel" />
+       </td>
+       <td align="center">
+        <al-input type="submit" class="butt" name="okay" value="Okay" />
+       </td>
+      </tr>
+     </table>
+    </al-expand>
+   </td>
+  </tr>
+ </table>
+ <al-input name="selected" type="hidden" />
+ <al-input name="cutsel" type="hidden" />
+ <script>
+  attachClickOpenOne('feq_edit', 'appform', 'selected');
+  jsselect('feq_edit', fe_select('appform', 'cutsel', 
+                ['Cancel', 'Copy', 'Cut']));
+  attachExpandOnHover('feq_edit');
+ </script>
+</al-expand>
+
diff --git a/pages/admin_form_edit_question.py b/pages/admin_form_edit_question.py
new file mode 100644
index 0000000..535e8c4
--- /dev/null
+++ b/pages/admin_form_edit_question.py
@@ -0,0 +1,90 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import copy
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.form_meta.has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        raise NotImplementedError()
+
+    def rollback(self, ctx):
+        ctx.locals.question.rollback()
+
+    def do_cancel(self, ctx, ignore):
+        # Don't use common method as we can't check for modifications to the
+        # question object (yet).
+        self.rollback(ctx)
+        ctx.pop_page()
+
+    def do_okay(self, ctx, ignore):
+        if self.first_error is None:
+            ctx.locals.question.commit()
+            ctx.pop_page()
+        else:
+            ctx.locals.selected = 'input_%s' % self.first_error
+
+    def do_op(self, ctx, *args):
+        selected = ctx.locals.question.op(*args)
+        if selected is not None:
+            ctx.locals.selected = 'input_%s' % selected
+
+    def _labelidx(self, ctx):
+        return [int(label.replace('select:input_', '')) 
+                for label in ctx.locals.cutsel.split(',')]
+
+    def do_copy(self, ctx, ignore):
+        ctx.locals.feq_cutbuff = ctx.locals.question.copy(self._labelidx(ctx))
+
+    def do_cut(self, ctx, ignore):
+        ctx.locals.feq_cutbuff = ctx.locals.question.cut(self._labelidx(ctx))
+        ctx.locals.selected = 'question'
+
+    def do_paste(self, ctx, index):
+        if ctx.locals.feq_cutbuff:
+            index = int(index)
+            ctx.locals.question.paste(index, ctx.locals.feq_cutbuff)
+            ctx.locals.selected = 'input_%s' % index
+
+
+pageops = PageOps()
+
+def page_enter(ctx, question):
+    ctx.locals.question = question
+    ctx.locals.selected = 'question'
+    ctx.add_session_vars('question', 'selected')
+
+def page_leave(ctx):
+    ctx.del_session_vars('question', 'selected')
+
+def page_display(ctx):
+    ctx.locals.form_disabled = True
+    ctx.run_template('admin_form_edit_question.html')
+
+def page_process(ctx):
+    ctx.locals.question.clear_err()
+    for input in ctx.locals.question.inputs:
+        input.page_process()
+    pageops.first_error = ctx.locals.question.check()
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_form_preview.html b/pages/admin_form_preview.html
new file mode 100644
index 0000000..81e6724
--- /dev/null
+++ b/pages/admin_form_preview.html
@@ -0,0 +1,33 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others. 
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="form.html">
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Preview form - 
+  <al-value expr="form_meta.name"> - 
+  <al-value expr="form_meta.root.text"></al-setarg>
+
+ <div class="syndrome-form">
+  <al-exec expr="form = form_meta.to_form()" />
+  <al-expand name="form_page" />
+ </div>
+
+</al-expand>
diff --git a/pages/admin_form_preview.py b/pages/admin_form_preview.py
new file mode 100644
index 0000000..c3213e2
--- /dev/null
+++ b/pages/admin_form_preview.py
@@ -0,0 +1,46 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others. 
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+
+from cocklebur import form_ui
+
+from pages import page_common
+
+import config
+
+class DummyFormData:
+    summary_id = None
+
+class PageOps(page_common.PageOpsBase):
+    pass
+
+page_process = PageOps().page_process
+
+def page_enter(ctx):
+    ctx.locals.form_data = DummyFormData()
+    ctx.locals.form_errors = form_ui.FormErrors()
+    ctx.add_session_vars('form_data', 'form_errors')
+
+def page_leave(ctx):
+    ctx.del_session_vars('form_data', 'form_errors')
+
+def page_display(ctx):
+    ctx.locals.form_disabled = False
+    ctx.locals.curnode = None
+    ctx.locals.showhelp = None
+    ctx.run_template('admin_form_preview.html')
diff --git a/pages/admin_forms.html b/pages/admin_forms.html
new file mode 100644
index 0000000..79473fb
--- /dev/null
+++ b/pages/admin_forms.html
@@ -0,0 +1,74 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Forms</al-setarg>
+  <table border="0" class="gridtab">
+   <thead class="darker">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="8"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <al-expand name="sort_header" />
+     <th>
+      <al-input name="add" class="smallbutt" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot>
+     <tr><td colspan="8"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="forms_i" expr="paged_search.result_page()">
+     <al-exec expr="form = forms_i.value()" />
+     <al-if expr="forms_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <td><al-value expr="form.label" /></td>
+      <td width="100%"><al-value expr="form.name" /></td>
+      <td align="right"><al-value expr="form.cur_version" /></td>
+      <td>
+       <al-if expr="form.def_update_time">
+        <al-value expr="form.def_update_time" />
+       </al-if>
+      </td>
+      <td><al-value expr="form.form_type" /></td>
+      <td><al-value lookup="boolean" expr="form.allow_multiple" /></td>
+      <td>
+       <al-input nameexpr="'edit:%s' % form.label" 
+            class="smallbutt" type="submit" value="Edit" />
+      </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="8" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_forms.py b/pages/admin_forms.py
new file mode 100644
index 0000000..8147fda
--- /dev/null
+++ b/pages/admin_forms.py
@@ -0,0 +1,58 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, datetime
+from casemgr.admin import formedit
+from casemgr import globals, paged_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add(self, ctx, ignore):
+        ctx.locals.form_result.reset()
+        ctx.push_page('admin_form_edit', None)
+
+    def do_edit(self, ctx, name):
+        ctx.locals.form_result.reset()
+        ctx.push_page('admin_form_edit', name)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, form_result):
+    form_result.headers = [
+        ('label', 'Name'),
+        ('name', 'Label'),
+        ('cur_version', 'Active Vers'),
+        ('def_update_time', 'Last Edit'),
+        ('form_type', 'Type'),
+        ('allow_multiple', 'Multi'),
+    ]
+    ctx.locals.form_result = form_result
+    paged_search.push_pager(ctx, ctx.locals.form_result)
+    ctx.add_session_vars('form_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('form_result')
+
+def page_display(ctx):
+    ctx.run_template('admin_forms.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_group.html b/pages/admin_group.html
new file mode 100644
index 0000000..495824e
--- /dev/null
+++ b/pages/admin_group.html
@@ -0,0 +1,85 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.group_label" /> <al-value expr="repr(group.group_name)" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <th colspan="2">Edit <al-value expr="config.group_label" /></th>
+   </tr>
+   <tr>
+    <td align="right">Name:</td>
+    <td><al-input name="group.group_name" size="40" /></td>
+   </tr>
+   <tr>
+    <td align="right">Description:</td>
+    <td><al-input name="group.description" size="40" /></td>
+   </tr>
+   <tr>
+    <td align="right"><al-value expr="config.syndrome_label">:</td>
+    <td>
+     <ul>
+      <al-for iter="s" expr="syndrome_names">
+       <li><al-value expr="s.value()" /></li>
+      </al-for>
+     </ul>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Rights:</td>
+    <td>
+     <table width="100%" class="gridtab">
+      <al-for iter="r_i" expr="credentials.Rights.available">
+       <tr class="darker">
+        <td>
+         <al-input type="checkbox" name="rights" list
+          valueexpr="r_i.value().right" />
+        </td>
+        <td>
+         <al-value expr="r_i.value().label" />
+         <al-if expr="r_i.value().desc">
+          <span class="smaller"> (<al-value expr="r_i.value().desc"/>)</span>
+         </al-if>
+        </td>
+       </tr>
+      </al-for>
+     </table>
+    </td>
+   </tr>
+   <tr>
+    <td colspan="2" class="darkest">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="center" width="100%">
+         <al-input class="butt danger" name="delete" type="submit" value="delete" />
+        </td>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   </tr>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_group.py b/pages/admin_group.py
new file mode 100644
index 0000000..cae1ca6
--- /dev/null
+++ b/pages/admin_group.py
@@ -0,0 +1,87 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj
+from casemgr import credentials, globals, syndrome
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        ctx.locals.group.rights = str(credentials.Rights(ctx.locals.rights))
+        if ctx.locals.group.db_has_changed():
+            raise page_common.ConfirmSave
+            
+    def commit(self, ctx):
+        group = ctx.locals.group
+        if not group.group_name:
+            raise page_common.PageError('Please specify a name')
+        if not group.description:
+            raise page_common.PageError('Please specify a description')
+        group.rights = str(credentials.Rights(ctx.locals.rights))
+        is_new = group.is_new()
+        ctx.admin_log(group.db_desc())
+        group.db_update()
+        globals.db.commit()
+        units = credentials.group_units(globals.db, group.group_id)
+        globals.notify.notify('units', *units)
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.add_message('Updated %r' % ctx.locals.group.group_name)
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        if not ctx.locals.group.is_new():
+            if not self.confirmed:
+                raise page_common.ConfirmDelete
+            try:
+                ctx.locals.group.db_delete()
+            except dbobj.ConstraintError:
+                ctx.locals.confirm = None
+                raise dbobj.ConstraintError('Can\'t delete in-use groups')
+            globals.db.commit()
+        ctx.add_message('Deleted %s %r' % (config.group_label, ctx.locals.group.group_name))
+        ctx.pop_page()
+
+pageops = PageOps()
+
+
+def page_enter(ctx, group_id):
+    if group_id is None:
+        ctx.locals.group = globals.db.new_row('groups')
+    else:
+        query = globals.db.query('groups')
+        query.where('group_id = %s', int(group_id))
+        ctx.locals.group = query.fetchone()
+    ctx.add_session_vars('group')
+    ctx.locals.rights = list(credentials.Rights(ctx.locals.group.rights))
+    ctx.add_session_vars('rights')
+
+def page_leave(ctx):
+    ctx.del_session_vars('group')
+    ctx.del_session_vars('rights')
+
+def page_display(ctx):
+    query = syndrome.query_by_group(ctx.locals.group.group_id)
+    ctx.locals.syndrome_names = query.fetchcols('name')
+    ctx.run_template('admin_group.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_groups.html b/pages/admin_groups.html
new file mode 100644
index 0000000..079d691
--- /dev/null
+++ b/pages/admin_groups.html
@@ -0,0 +1,69 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.group_label"></al-setarg>
+  <table border="0" class="gridtab">
+   <thead class="darkest">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="4"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+      <al-input name="add" class="smallbutt" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot class="darkest">
+     <tr><td colspan="4"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="groups_i" expr="paged_search.result_page()">
+     <al-exec expr="group = groups_i.value()" />
+     <al-if expr="groups_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+       <td><al-value expr="group.group_name" /></td>
+       <td width="100%"><al-value expr="group.description" /></td>
+       <td>
+        <al-if expr="group.rights"><al-value expr="group.rights" /></al-if>
+       </td>
+       <td align="center" nowrap>
+        <al-input nameexpr="'edit:%s' % group.group_id" 
+             class="smallbutt" type="submit" value="Edit" />
+       </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="4" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_groups.py b/pages/admin_groups.py
new file mode 100644
index 0000000..4ef8f20
--- /dev/null
+++ b/pages/admin_groups.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import paged_search
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_edit(self, ctx, group_id):
+        ctx.locals.group_result.reset()
+        ctx.push_page('admin_group', int(group_id))
+
+    def do_add(self, ctx, ignore):
+        ctx.locals.group_result.reset()
+        ctx.push_page('admin_group', None)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, group_result):
+    group_result.headers = [
+        ('group_name', 'Name'),
+        ('description', 'Description'),
+        ('rights', 'Rights'),
+    ]
+    ctx.locals.group_result = group_result
+    paged_search.push_pager(ctx, ctx.locals.group_result)
+    ctx.add_session_vars('group_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('group_result')
+
+def page_display(ctx):
+    ctx.run_template('admin_groups.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_queue.html b/pages/admin_queue.html
new file mode 100644
index 0000000..658cb73
--- /dev/null
+++ b/pages/admin_queue.html
@@ -0,0 +1,102 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Task Queue <al-value expr="repr(queue.name)" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <td colspan="3">
+     <table width="100%" cellpadding="0" cellspacing="0" border="0">
+      <tr>
+       <th width="100%">Edit queue</th>
+      </tr>
+     </table>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Task Queue Name:</td>
+    <td><al-input name="queue.name" size="40" /></td>
+    <td rowspan="2">
+     <al-if expr="queue_stats">
+      <div class="key">
+       Queue stats:
+       <table>
+        <al-for iter="qs" expr="queue_stats">
+         <al-exec expr="label, count = qs.value()" />
+         <tr>
+          <td><al-value expr="label" /></td>
+          <td><al-value expr="count" /></td>
+         </tr>
+        </al-for>
+       </table>
+      </div>
+     </al-if>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Description:</td>
+    <td><al-input name="queue.description" size="40" /></td>
+   </tr>
+   <tr>
+    <td align="right"><al-value expr="config.unit_label" />:</td>
+    <td colspan="2">
+     <al-exec expr="pt_search = unit_pt_search" />
+     <al-expand name="search_pt">
+      <al-setarg name="left_title">Selected</al-setarg>
+      <al-setarg name="left_row"><al-value expr="row.name" /></al-setarg>
+      <al-setarg name="right_title">Add/Search</al-setarg>
+      <al-setarg name="right_row"><al-value expr="row.name" /></al-setarg>
+     </al-expand>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Users:</td>
+    <td colspan="2">
+     <al-exec expr="pt_search = user_pt_search" />
+     <al-expand name="search_pt">
+      <al-setarg name="left_title">Selected</al-setarg>
+      <al-setarg name="left_row"><al-value expr="row.name" /></al-setarg>
+      <al-setarg name="right_title">Add/Search</al-setarg>
+      <al-setarg name="right_row"><al-value expr="row.name" /></al-setarg>
+     </al-expand>
+    </td>
+   </tr>
+
+   <tr>
+    <td colspan="3" class="darkest">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="center" width="100%">
+         <al-input class="butt danger" name="delete" type="submit" value="delete" />
+        </td>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   <tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_queue.py b/pages/admin_queue.py
new file mode 100644
index 0000000..92374ae
--- /dev/null
+++ b/pages/admin_queue.py
@@ -0,0 +1,113 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, pt
+from casemgr import globals, credentials, tasks
+from pages import page_common
+
+import config
+
+class ConfirmDelete(page_common.ConfirmDelete):
+    title = 'You are deleting this task queue'
+
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.unit_pt_search.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.user_pt_search.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.queue.db_has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        queue_updated = ctx.locals.queue.db_has_changed()
+        ctx.admin_log(ctx.locals.queue.db_desc())
+        ctx.locals.queue.db_update()
+        ctx.locals.unit_pt_search.set_key(ctx.locals.queue.queue_id)
+        ctx.admin_log(ctx.locals.unit_pt_search.db_desc())
+        ctx.locals.unit_pt_search.db_update()
+        ctx.locals.user_pt_search.set_key(ctx.locals.queue.queue_id)
+        ctx.admin_log(ctx.locals.user_pt_search.db_desc())
+        ctx.locals.user_pt_search.db_update()
+        globals.db.commit()
+        if queue_updated:
+            globals.notify.notify('workqueues', ctx.locals.queue.queue_id)
+        ctx.add_message('Updated queue %r' % ctx.locals.queue.name)
+
+    def revert(self, ctx):
+        ctx.locals.unit_pt_search.db_revert()
+        ctx.locals.user_pt_search.db_revert()
+        ctx.locals.queue.db_revert()
+
+    def do_update(self, ctx, ignore):
+        ctx.locals.unit_pt_search.clear_search_result()
+        ctx.locals.user_pt_search.clear_search_result()
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        if ctx.locals.queue.queue_id:
+            if ctx.locals.queue_stats.active:
+                raise page_common.PageError('workqueue %r cannot be deleted as it has outstanding tasks' % ctx.locals.queue.name)
+            if not self.confirmed:
+                raise ConfirmDelete(message='Deleting this task queue will irreversably delete %d completed task records' % ctx.locals.queue_stats.completed)
+            else:
+                tasks.delete_queue(ctx.locals.queue.queue_id)
+                globals.db.commit()
+                globals.notify.notify('workqueues', ctx.locals.queue.queue_id)
+        ctx.add_message('Deleted queue %r' % ctx.locals.queue.name)
+        ctx.pop_page()
+
+    def do_unit_pt_search(self, ctx, op, *args):
+        ctx.locals.unit_pt_search.do(op, *args)
+
+    def do_user_pt_search(self, ctx, op, *args):
+        ctx.locals.user_pt_search.do(op, *args)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, queue_id):
+    if queue_id is None:
+        ctx.locals.queue = globals.db.new_row('workqueues')
+        ctx.locals.queue_stats = None
+    else:
+        query = globals.db.query('workqueues')
+        query.where('queue_id = %s', queue_id)
+        ctx.locals.queue = query.fetchone()
+        ctx.locals.queue_stats = tasks.QueueStats(ctx.locals.queue.queue_id)
+    queue_units = globals.db.ptset('workqueue_members', 'queue_id', 
+                                   'unit_id', queue_id, 'user_id is null')
+    ctx.locals.unit_pt_search = pt.SearchPT(queue_units, 'name', 
+                                            name='unit_pt_search',
+                                            filter='enabled')
+    queue_users = globals.db.ptset('workqueue_members', 'queue_id', 
+                                   'user_id', queue_id, 'unit_id is null')
+    ctx.locals.user_pt_search = pt.SearchPT(queue_users, 'fullname', 
+                                            name='user_pt_search',
+                                            filter='enabled')
+    ctx.add_session_vars('queue', 'queue_stats', 'unit_pt_search', 'user_pt_search')
+
+def page_leave(ctx):
+    ctx.del_session_vars('queue', 'queue_stats', 'unit_pt_search', 'user_pt_search')
+
+def page_display(ctx):
+    ctx.run_template('admin_queue.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_queues.html b/pages/admin_queues.html
new file mode 100644
index 0000000..15d90b1
--- /dev/null
+++ b/pages/admin_queues.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Queues</al-setarg>
+  <table border="0" class="gridtab">
+   <thead class="darkest">
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+      <al-input name="add:" class="smallbutt" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot class="darkest">
+     <tr><td colspan="4"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="queues_i" expr="paged_search.result_page()">
+     <al-exec expr="queue = queues_i.value()" />
+     <al-if expr="queues_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+       <td><al-value expr="queue.name" /></td>
+       <td width="100%"><al-value expr="queue.description" /></td>
+       <td align="center" nowrap>
+        <al-input nameexpr="'edit:%s' % queue.queue_id" 
+             class="smallbutt" type="submit" value="Edit" />
+       </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="4" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_queues.py b/pages/admin_queues.py
new file mode 100644
index 0000000..3a7b21b
--- /dev/null
+++ b/pages/admin_queues.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add(self, ctx, ignore):
+        ctx.locals.queue_result.reset()
+        ctx.push_page('admin_queue', None)
+
+    def do_edit(self, ctx, group_id):
+        ctx.push_page('admin_queue', int(group_id))
+
+pageops = PageOps()
+
+
+def page_enter(ctx, queue_result):
+    queue_result.headers = [
+        ('name', 'Name'),
+        ('description', 'Description'),
+    ]
+    ctx.locals.queue_result = queue_result
+    paged_search.push_pager(ctx, ctx.locals.queue_result)
+    ctx.add_session_vars('queue_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('queue_result')
+
+def page_display(ctx):
+    ctx.run_template('admin_queues.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_synd_categ.html b/pages/admin_synd_categ.html
new file mode 100644
index 0000000..ac1676c
--- /dev/null
+++ b/pages/admin_synd_categ.html
@@ -0,0 +1,76 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-script srcexpr="appath('admin.js')" type="text/javascript"></al-script>
+ <al-setarg name="title"><al-value expr="title" /></al-setarg>
+  <table border="0" class="admin-syndromes" id="admin-syndcateg">
+   <thead>
+    <tr>
+     <al-if expr="syndcateg.explicit_order">
+      <th></th>
+     </al-if>
+     <th>Name</th>
+     <th>Label</th>
+     <th>
+      <al-input type="image" height="24" width="24" alt="add"
+        srcexpr="appath('images/button-add.png')" nameexpr="'add:'" />
+     </th>
+    </tr>
+   </thead>
+   <tfoot>
+    <tr>
+     <td colspan="4" class="darker">
+      <al-expand name="confirm_or_error">
+       <table width="100%" cellpadding="0" cellspacing="0">
+        <tr>
+         <td align="right">
+          <al-input class="butt" name="update" type="submit" value="Save" />
+          <script>enterSubmit('appform', 'update');</script>
+         </td>
+        </tr>
+       </table>
+      </al-expand>
+     </td>
+    </tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="cs_i" expr="syndcateg">
+     <tr>
+      <al-if expr="syndcateg.explicit_order">
+       <td class="darker"><button class="move-handle">◈</button></td>
+       </td>
+      </al-if>
+      <td><al-input nameexpr="'syndcateg[%d].name' % cs_i.index()" /></td>
+      <td><al-input nameexpr="'syndcateg[%d].label' % cs_i.index()" size="36" ></td>
+      <td>
+       <al-input type="image" height="24" width="24" alt="del"
+         srcexpr="appath('images/button-del.png')" 
+         nameexpr="'del:%d' % cs_i.index()" />
+      </td>
+     </tr>
+    </al-for>
+   </tbody>
+  </table>
+  <al-if expr="syndcateg.explicit_order">
+   <al-input type="hidden" name="syndcateg.order" />
+   <script>rowMover('admin-syndcateg', 'syndcateg.order');</script>
+  </al-if>
+</al-expand>
diff --git a/pages/admin_synd_categ.py b/pages/admin_synd_categ.py
new file mode 100644
index 0000000..b307d2a
--- /dev/null
+++ b/pages/admin_synd_categ.py
@@ -0,0 +1,74 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsLeaf):
+    def unsaved_check(self, ctx):
+         if ctx.locals.syndcateg.has_changed():
+            ctx.add_error('WARNING: %s changes discarded' %
+                           ctx.locals.syndcateg.title)
+
+    def commit(self, ctx):
+        if ctx.locals.syndcateg.has_changed():
+            ctx.locals.syndcateg.update()
+            globals.db.commit()
+            globals.notify.notify('syndromes')
+        ctx.add_message('Updated %s' % ctx.locals.syndcateg.title)
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_up(self, ctx, index):
+        ctx.locals.syndcateg.move_up(int(index))
+
+    def do_dn(self, ctx, index):
+        ctx.locals.syndcateg.move_down(int(index))
+
+    def do_del(self, ctx, index):
+        ctx.locals.syndcateg.delete(int(index))
+
+    def do_add(self, ctx, ignore):
+        ctx.locals.syndcateg.new()
+
+
+pageops = PageOps()
+
+def page_enter(ctx, syndcateg):
+    ctx.locals.syndcateg = syndcateg
+    ctx.add_session_vars('syndcateg')
+
+def page_leave(ctx):
+    ctx.del_session_vars('syndcateg')
+
+def page_display(ctx):
+    try:
+        synd = ctx.locals.syndrome
+    except AttributeError:
+        ctx.locals.title = 'Common %s' % ctx.locals.syndcateg.title
+    else:
+        ctx.locals.title = '%s for %s' % (ctx.locals.syndcateg.title, synd.name)
+    ctx.run_template('admin_synd_categ.html')
+
+def page_process(ctx):
+    ctx.locals.syndcateg.reorder()
+    pageops.page_process(ctx)
diff --git a/pages/admin_synd_clear.html b/pages/admin_synd_clear.html
new file mode 100644
index 0000000..96bfda5
--- /dev/null
+++ b/pages/admin_synd_clear.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+<al-include name="group_edit.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Clearing <al-value expr="config.syndrome_label" /> <al-value expr="syndrome.name" /></al-setarg>
+ <div class="centbox">
+  <h1>CLEARING <al-value expr="config.syndrome_label" /></h1>
+  <al-if expr="syndrome.description">
+   <div class="quote">
+    <h1><al-value expr="syndrome.name" /></h1>
+    <al-value expr="wiki_text(syndrome.description)" noescape="noescape" />
+   </div>
+  </al-if>
+  <al-if expr="clear_syndrome.errors">
+   <div><al-value expr="config.syndrome_label"> cannot be cleared:</div>
+   <al-for iter="e" expr="clear_syndrome.errors">
+    <div class="err"><al-value expr="e.value()" /></div>
+   </al-for>
+  </al-if>
+  <al-if expr="clear_syndrome.case_count is not None">
+   <div>Clearing this <al-value expr="config.syndrome_label" /> will 
+    irreversably delete <al-value expr="clear_syndrome.case_count" /> 
+    case(s), <al-value expr="clear_syndrome.form_count" /> form(s) and
+    <al-value expr="clear_syndrome.task_count" /> task(s). 
+    <al-if expr="not have_errors() and not clear_syndrome.errors">
+     It is recommended that you back up your data before performing
+     this operation.<br>
+     <b>Check this box to acknowledge the above:</b>
+     <al-input type="checkbox" name="clear_syndrome.acknowledge" value="ACK" />
+    </al-if>
+   </div>
+  </al-if>
+  <table width="100%">
+   <tr>
+    <td align="right">
+     <al-if expr="not have_errors() and not clear_syndrome.errors">
+      <al-input class="bigbutt danger" name="delete" type="submit" value="Delete (data only)" />
+     </al-if>
+    </td>
+   </tr>
+  </table>
+ </div>
+</al-expand>
diff --git a/pages/admin_synd_clear.py b/pages/admin_synd_clear.py
new file mode 100644
index 0000000..cb8112f
--- /dev/null
+++ b/pages/admin_synd_clear.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+from casemgr import globals
+from casemgr.admin import dropsyndrome
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_delete(self, ctx, ignore):
+        ctx.locals.clear_syndrome.delete(ctx.locals.clear_syndrome.case_count,
+                                         ctx.locals.clear_syndrome.form_count)
+        globals.db.commit()
+        ctx.pop_page()
+        ctx.pop_page()
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    syndrome_id = ctx.locals.syndrome.syndrome_id
+    ctx.locals.clear_syndrome = dropsyndrome.ClearSyndrome(syndrome_id)
+    ctx.add_session_vars('clear_syndrome')
+
+def page_leave(ctx):
+    ctx.del_session_vars('clear_syndrome')
+
+def page_display(ctx):
+    try:
+        ctx.locals.clear_syndrome.update_counts()
+    except globals.Error, e:
+        ctx.add_error(e)
+    ctx.run_template('admin_synd_clear.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_synd_drop.html b/pages/admin_synd_drop.html
new file mode 100644
index 0000000..8ad6a07
--- /dev/null
+++ b/pages/admin_synd_drop.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+<al-include name="group_edit.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Delete <al-value expr="config.syndrome_label" /></al-setarg>
+ <div class="centbox">
+  <h1>DELETE <al-value expr="config.syndrome_label" /></h1>
+  <al-if expr="syndrome.description">
+   <div class="quote">
+    <h1><al-value expr="syndrome.name" /></h1>
+    <al-value expr="wiki_text(syndrome.description)" noescape="noescape" />
+   </div>
+  </al-if>
+  <al-if expr="drop_syndrome.errors">
+   <div><al-value expr="config.syndrome_label"> cannot be deleted:</div>
+   <al-for iter="e" expr="drop_syndrome.errors">
+    <div class="err"><al-value expr="e.value()" /></div>
+   </al-for>
+  </al-if>
+  <al-if expr="drop_syndrome.case_count is not None">
+   <div>Deleting this <al-value expr="config.syndrome_label" /> will 
+    <b>irreversably</b> delete <al-value expr="drop_syndrome.case_count" /> 
+    case(s), <al-value expr="drop_syndrome.form_count" /> form(s) and
+    <al-value expr="drop_syndrome.task_count" /> task(s). 
+    <al-if expr="not have_errors() and not drop_syndrome.errors">
+     It is recommended that you back up your data before performing
+     this operation.<br>
+     <b>Check this box to acknowledge the above:</b>
+     <al-input type="checkbox" name="drop_syndrome.acknowledge" value="ACK" />
+    </al-if>
+   </div>
+  </al-if>
+  <table width="100%">
+   <tr>
+    <td align="right">
+     <al-if expr="not have_errors() and not drop_syndrome.errors">
+      <al-input class="butt danger" name="delete" type="submit" value="Delete" />
+     </al-if>
+    </td>
+   </tr>
+  </table>
+ </div>
+</al-expand>
diff --git a/pages/admin_synd_drop.py b/pages/admin_synd_drop.py
new file mode 100644
index 0000000..ebadea7
--- /dev/null
+++ b/pages/admin_synd_drop.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+
+from casemgr import globals
+from casemgr.admin import dropsyndrome
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_delete(self, ctx, ignore):
+        ctx.locals.drop_syndrome.delete(ctx.locals.drop_syndrome.case_count,
+                                        ctx.locals.drop_syndrome.form_count)
+        globals.db.commit()
+        ctx.pop_page()
+        ctx.pop_page()
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    syndrome_id = ctx.locals.syndrome.syndrome_id
+    ctx.locals.drop_syndrome = dropsyndrome.DropSyndrome(syndrome_id)
+    ctx.add_session_vars('drop_syndrome')
+
+def page_leave(ctx):
+    ctx.del_session_vars('drop_syndrome')
+
+def page_display(ctx):
+    try:
+        ctx.locals.drop_syndrome.update_counts()
+    except globals.Error, e:
+        ctx.add_error(e)
+    ctx.run_template('admin_synd_drop.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_synd_fields.html b/pages/admin_synd_fields.html
new file mode 100644
index 0000000..c89691e
--- /dev/null
+++ b/pages/admin_synd_fields.html
@@ -0,0 +1,143 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+<al-include name="group_edit.html" />
+
+<al-macro name="asf-contexts">
+     <al-for iter="c_i" expr="contexts">
+      <th class="smaller">
+       <al-value expr="c_i.value()[0].upper()" />
+      </th>
+     </al-for>
+</al-macro>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="title" /></al-setarg>
+  <div class="key">
+   <table>
+    <thead>
+     <tr>
+      <th colspan="3">KEY</th>
+     </tr>
+    </thead>
+    <tbody>
+     <tr>
+      <th><u>C</u>ase</th>
+      <td>Editable case demographic fields.</td>
+     </tr>
+     <tr>
+      <th><u>F</u>orm</th>
+      <td>Non-editable case demographic fields shown while
+      editing forms, access control and tasks.</td>
+     </tr>
+     <tr>
+      <th><u>S</u>earch</th>
+      <td>Fields available for Search and Edit Case.</td>
+     </tr>
+     <tr>
+      <th><u>P</u>erson</th>
+      <td>Fields available when adding a Case (duplicate person search).</td>
+     </tr>
+     <tr>
+      <th><u>R</u>esult</th>
+      <td>Fields that appear in the search results.</td>
+     </tr>
+    </tbody>
+   </table>
+  </div>
+
+  <table border="0" class="admin-syndromes">
+   <col align="left">
+   <col align="center">
+   <col align="left">
+   <al-colgroup align="center" spanexpr="len(contexts)" />
+   <colgroup span="2" align="center"></colgroup>
+   <thead>
+    <tr>
+     <th>Field Name</th>
+     <th class="smaller" style="text-align: center;">Entity</th>
+     <th>Field Label</th>
+     <al-expand name="asf-contexts" />
+     <th colspan="2"></th>
+    </tr>
+   </thead>
+   <al-for iter="dfg_i" expr="demog_fields.grouped()">
+    <al-exec expr="dfg = dfg_i.value()" />
+    <al-if expr="dfg.label">
+     <tr>
+      <th colspan="3" class="smaller">
+       <al-if expr="cur == dfg.name"><a name="cur" /></al-if>
+       <al-value expr="dfg.label" />
+      </th>
+      <al-expand name="asf-contexts" />
+      <th colspan="2"></th>
+     </tr>
+    </al-if>
+    <al-for iter="df_i" expr="dfg">
+     <al-exec expr="df = df_i.value()">
+     <al-exec expr="i = demog_fields.index(df)" />
+     <al-exec expr="bn = 'demog_fields[%d]' % i">
+     <tr>
+      <td><al-value expr="df.name" /></td>
+      <td class="smaller"><al-value expr="df.entity" /></td>
+      <td><al-input nameexpr="bn + '.label'" size="24" /></td>
+      <al-for iter="c_i" expr="contexts">
+       <td>
+        <al-if expr="df.allow_field(c_i.value())">
+         <al-input type="checkbox" value="True"
+              disabledbool="not df.hideable"
+              nameexpr="'%s.show_%s' % (bn, c_i.value())" />
+        </al-if>
+       </td>
+      </al-for>
+      <td><al-input type="submit" value="DFL" style="width: 2em;"
+            nameexpr="'dflt:%s:%s' % (dfg.name, i)" /></td>
+      <al-if expr="df_i.index() == 0">
+       <al-th rowspanexpr="len(dfg)">
+        <al-if expr="len(dfg) > 1">
+         <al-input type="submit" value="+" style="width: 2em;"
+             nameexpr="'grp:on:%s' % dfg.name" /><br>
+         <al-input type="submit" value="DFL" style="width: 2em;"
+             nameexpr="'grp:dflt:%s' % dfg.name" /><br>
+         <al-input type="submit" value="-" style="width: 2em;"
+             nameexpr="'grp:off:%s' % dfg.name" />
+        </al-if>
+       </al-th>
+      </al-if>
+     </tr>
+    </al-for>
+   </al-for>
+   <tr>
+    <al-td colspanexpr="5 + len(contexts)" class="darker">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </al-td>
+   </tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_synd_fields.py b/pages/admin_synd_fields.py
new file mode 100644
index 0000000..f37840f
--- /dev/null
+++ b/pages/admin_synd_fields.py
@@ -0,0 +1,83 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import demogfields, globals
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsLeaf):
+    def unsaved_check(self, ctx):
+        if ctx.locals.demog_fields.has_changed():
+            ctx.add_error('WARNING: demographic field changes discarded')
+
+    def commit(self, ctx):
+        ctx.locals.demog_fields.update(globals.db)
+        globals.db.commit()
+        ctx.add_message('Updated demographic fields')
+        globals.notify.notify('demogfields', 
+                              ctx.locals.demog_fields.syndrome_id)
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_dflt(self, ctx, group, index):
+        index = int(index)
+        ctx.locals.demog_fields[index].reset()
+        ctx.locals.cur = group
+
+    def do_grp(self, ctx, op, group):
+        if group == 'None':
+            group = None
+        fields = ctx.locals.demog_fields.group(group)
+        ctx.locals.cur = group
+        if op == 'on':
+            for field in fields:
+                field.set(True)
+        elif op == 'dflt':
+            for field in fields:
+                field.reset()
+        else:
+            for field in fields:
+                field.set(False)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.demog_fields = demogfields.DemogFields(globals.db, syndrome_id,
+                                                      save_initial=True)
+    ctx.locals.cur = None
+    ctx.add_session_vars('demog_fields', 'cur')
+
+def page_leave(ctx):
+    ctx.del_session_vars('demog_fields', 'cur')
+
+def page_display(ctx):
+    ctx.locals.contexts = demogfields.contexts
+    try:
+        synd = ctx.locals.syndrome
+    except AttributeError:
+        ctx.locals.title = 'Global %s Fields' % config.syndrome_label
+    else:
+        ctx.locals.title = '%s Fields for %s' %\
+            (config.syndrome_label, synd.name)
+    ctx.run_template('admin_synd_fields.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_syndrome.html b/pages/admin_syndrome.html
new file mode 100644
index 0000000..cf4cccf
--- /dev/null
+++ b/pages/admin_syndrome.html
@@ -0,0 +1,131 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+<al-include name="group_edit.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.syndrome_label" /></al-setarg>
+  <table border="0" class="admin-syndromes toptable" width="95%">
+   <tr>
+    <th colspan="3">
+     <table width="100%" cellspacing="0" cellpadding="0">
+      <tr>
+       <td align="left">Edit <al-value expr="config.syndrome_label" /></td>
+       <td align="right">
+        <al-if expr="syndrome.syndrome_id is not None">
+         <al-input class="bigbutt" type="submit" name="case_assignment" 
+          value="Assignment Values" />
+         <al-input class="bigbutt" type="submit" name="case_status" 
+          value="Status Values" />
+         <al-input class="bigbutt" type="submit" name="demog_fields" 
+          value="Demog. Fields" />
+        </al-if>
+       </td>
+      </tr>
+     </table>
+    </th>
+   </tr>
+   <tr>
+    <td><label for="syndrome.name">Name</label></td>
+    <td colspan="2"><al-input name="syndrome.name" size="60" /></td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.priority">Priority</label></td>
+    <td colspan="2">
+     <al-select name="syndrome.priority" optionexpr="range(1,6)"
+       style="width: 3em;" /></td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.description">Description<br><al-input class="butt" name="wikiedit:description" type="submit" value="Edit" /></label></td>
+    <td colspan="2">
+     <al-if expr="syndrome.description"><div class="preview"><al-value expr="wiki_text(syndrome.description)" noescape="noescape" /></div></al-if></td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.additional_info">Additional Information<br><al-input class="butt" name="wikiedit:additional_info" type="submit" value="Edit" /></label></td>
+    <td colspan="2">
+     <al-if expr="syndrome.additional_info"><div class="preview"><al-value expr="wiki_text(syndrome.additional_info)" noescape="noescape" /></div></al-if></td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.enabled">Enabled</label></td>
+    <td align="left">
+     <al-input id="syndrome_enabled" name="syndrome.enabled" 
+       type="radio" value="True">Yes
+     <al-input id="syndrome_disabled" name="syndrome.enabled" 
+       type="radio" value="">No
+    </td>
+    <td rowspan="3" class="status">
+     <b>Status</b><br>
+     <al-for iter="i" expr="synd_status">
+      <al-value expr="i.value()" /><br>
+     </al-for>
+    </td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.post_date">Post Date</label></td>
+    <td>
+     <al-input name="syndrome.post_date" id="syndrome.post_date" size="40" 
+     calendarformatexpr="datetime.mx_parse_datetime.format" /></td>
+   </tr>
+   <tr>
+    <td><label for="syndrome.expiry_date">Expiry Date</label></td>
+    <td>
+     <al-input name="syndrome.expiry_date" id="syndrome.expiry_date" 
+     size="40" calendarformatexpr="datetime.mx_parse_datetime.format" />
+     <div class="smaller">(leave blank for "Never")</div>
+    </td>
+   </tr>
+   <tr>
+    <td><label>Forms</label></td>
+    <td colspan="2">
+     <al-expand name="search_pt">
+      <al-setarg name="left_title">Selected</al-setarg>
+      <al-setarg name="right_title">Available</al-setarg>
+     </al-expand>
+    </td>
+   </tr>
+   <tr>
+    <td><label><al-value expr="config.group_label" /></label></td>
+    <td colspan="2">
+     <al-expand name="group_edit" />
+    </td>
+   </tr>
+   <tr>
+    <td colspan="3">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0" class="darker">
+       <tr>
+        <td align="center" width="100%">
+         <al-if expr="syndrome.syndrome_id is not None">
+          <al-input class="butt danger" name="clear" type="submit" value="clear" />
+          <al-input class="butt danger" name="delete" type="submit" value="delete" />
+         </al-if>
+        </td>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Update" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   </tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_syndrome.py b/pages/admin_syndrome.py
new file mode 100644
index 0000000..53129e5
--- /dev/null
+++ b/pages/admin_syndrome.py
@@ -0,0 +1,169 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, pt, datetime
+from casemgr import globals
+from casemgr.casestatus import EditSyndromeCaseStates
+from casemgr.caseassignment import EditSyndromeCaseAssignment
+from pages import page_common
+import config
+
+def normalise_date(label, dt):
+    if dt is not None:
+        try:
+            return str(datetime.mx_parse_datetime(dt))
+        except datetime.Error, e:
+            raise page_common.PageError('%s: %s' % (label, e))
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.pt_search.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.group_edit.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.syndrome.db_has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        synd = ctx.locals.syndrome
+        synd.post_date = normalise_date('Post date', synd.post_date)
+        synd.expiry_date = normalise_date('Expiry date', synd.expiry_date)
+        ctx.admin_log(synd.db_desc())
+        try:
+            synd.db_update()
+        except dbobj.DuplicateKeyError:
+            raise dbobj.DuplicateKeyError('Name is already used - choose another')
+        ctx.locals.pt_search.set_key(synd.syndrome_id)
+        ctx.admin_log(ctx.locals.pt_search.db_desc())
+        ctx.locals.pt_search.db_update()
+        ctx.locals.group_edit.set_key(synd.syndrome_id)
+        ctx.admin_log(ctx.locals.group_edit.db_desc())
+        ctx.locals.group_edit.db_update()
+        globals.db.commit()
+        ctx.add_message('Updated %s %r' % (config.syndrome_label.lower(), synd.name))
+        globals.notify.notify('syndromes')
+        globals.notify.notify('syndrome_units')
+
+    def revert(self, ctx):
+        ctx.locals.syndrome.db_revert()
+        ctx.locals.pt_search.db_revert()
+        ctx.locals.group_edit.db_revert()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+
+    def do_form_search(self, ctx, ignore):
+        ctx.locals.pt_search.search('name')
+
+    def do_demog_fields(self, ctx, ignore):
+        assert ctx.locals.syndrome.syndrome_id is not None
+        ctx.push_page('admin_synd_fields', ctx.locals.syndrome.syndrome_id)
+
+    def do_case_status(self, ctx, ignore):
+        assert ctx.locals.syndrome.syndrome_id is not None
+        syndcateg = EditSyndromeCaseStates(ctx.locals.syndrome.syndrome_id)
+        ctx.push_page('admin_synd_categ', syndcateg)
+
+    def do_case_assignment(self, ctx, ignore):
+        assert ctx.locals.syndrome.syndrome_id is not None
+        syndcateg = EditSyndromeCaseAssignment(ctx.locals.syndrome.syndrome_id)
+        ctx.push_page('admin_synd_categ', syndcateg)
+
+    def do_select(self, ctx, which, op):
+        assert op in ('add', 'remove')
+        select_pt = getattr(ctx.locals, which)
+        meth = getattr(select_pt, op)
+        meth()
+
+    def do_pt_search(self, ctx, op, *args):
+        ctx.locals.pt_search.do(op, *args)
+
+    def do_group_edit(self, ctx, op, *args):
+        ctx.locals.group_edit.do(op, *args)
+
+    def do_wikiedit(self, ctx, field):
+        prompt = {
+            'description': 'Description',
+            'additional_info': 'Additional Information',
+        }[field]
+        ctx.push_page('admin_wikiedit', ctx.locals.syndrome, field, prompt,
+                      '%s %s' % (config.syndrome_label, 
+                                 ctx.locals.syndrome.name),
+                      'admin-syndromes')
+
+    def do_delete(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        if ctx.locals.syndrome.syndrome_id is not None:
+            ctx.push_page('admin_synd_drop')
+
+    def do_clear(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        if ctx.locals.syndrome.syndrome_id is not None:
+            ctx.push_page('admin_synd_clear')
+
+
+pageops = PageOps()
+
+
+def page_enter(ctx, syndrome_id):
+    if syndrome_id is None:
+        ctx.locals.syndrome = globals.db.new_row('syndrome_types',
+                                                 priority=3)
+    else:
+        query = globals.db.query('syndrome_types')
+        query.where('syndrome_id = %s', syndrome_id)
+        ctx.locals.syndrome = query.fetchone()
+    ctx.add_session_vars('syndrome')
+
+    # Syndrome forms
+    synd_forms_pt = globals.db.participation_table('syndrome_forms',
+                                                   'syndrome_id', 'form_label')
+    synd_forms_pt.preload_from_result([ctx.locals.syndrome])
+    synd_form_pt = synd_forms_pt[syndrome_id]
+    ctx.locals.pt_search = pt.OrderedSearchPT(synd_form_pt, 'label', 
+                                              filter='cur_version is not null')
+    ctx.add_session_vars('pt_search')
+
+    # Syndrome groups
+    synds_groups_pt = globals.db.participation_table('group_syndromes',
+                                                     'syndrome_id', 'group_id')
+    synds_groups_pt.preload_from_result([ctx.locals.syndrome])
+    synds_groups_pt.get_slave_cache().preload_all()
+    synd_groups_pt = synds_groups_pt[syndrome_id]
+    ctx.locals.group_edit = pt.SelectPT(synd_groups_pt, 'group_name')
+    ctx.add_session_vars('group_edit')
+
+
+def page_leave(ctx):
+    ctx.del_session_vars('syndrome', 'search_pt', 'group_edit')
+
+
+def page_display(ctx):
+    if ctx.locals.syndrome.syndrome_id is not None:
+        try:
+            synd = ctx.locals.syndromes[ctx.locals.syndrome.syndrome_id]
+        except LookupError:
+            ctx.locals.synd_status = ['Unknown']
+        else:
+            ctx.locals.synd_status = synd.desc_status()
+    else:
+        ctx.locals.synd_status = ['New %s' % config.syndrome_label]
+    ctx.run_template('admin_syndrome.html')
+
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_syndromes.html b/pages/admin_syndromes.html
new file mode 100644
index 0000000..37c8557
--- /dev/null
+++ b/pages/admin_syndromes.html
@@ -0,0 +1,72 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.syndrome_label" /></al-setarg>
+  <table border="0" cellpadding="2" class="gridtab">
+   <thead>
+    <tr>
+     <al-expand name="sort_header" />
+     <th class="center buttons">
+      <al-input name="add" class="smallbutt" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot class="darkest">
+     <tr><td colspan="8"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="synd_i" expr="page">
+     <al-exec expr="synd = synd_i.value()" />
+     <al-if expr="synd_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <td class="center">
+       <al-value expr="synd.priority" />
+      </td>
+      <td>
+       <al-if expr="synd.active()">
+        <al-value expr="synd.name" />
+       <al-else>
+        <strike><al-value expr="synd.name" /></strike>
+       </al-if>
+      </td>
+      <td>
+       <al-value expr="groups_pt[synd.syndrome_id].comma_list('group_name')" />
+      </td>
+      <td nowrap class="center">
+       <al-for vars="l" expr="synd.desc_status()">
+        <al-value expr="l" /><br>
+       </al-for>
+      </td>
+      <td class="center buttons">
+       <al-input nameexpr="'edit:%s' % synd.syndrome_id" 
+            class="smallbutt" type="submit" value="Edit" />
+      </td>
+     </tr>
+    </al-for>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_syndromes.py b/pages/admin_syndromes.py
new file mode 100644
index 0000000..4406b65
--- /dev/null
+++ b/pages/admin_syndromes.py
@@ -0,0 +1,65 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, datetime
+from casemgr import globals, syndrome, paged_search
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_edit(self, ctx, syndrome_id):
+        ctx.locals.synd_result.reset()
+        ctx.push_page('admin_syndrome', int(syndrome_id))
+
+    def do_add(self, ctx, ignore):
+        ctx.locals.synd_result.reset()
+        ctx.push_page('admin_syndrome', None)
+
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, synd_result):
+    synd_result.headers = [
+        ('priority', 'Prio'),
+        ('name', 'Name'),
+        (None, config.group_label),
+        (None, 'Status'),
+    ]
+    ctx.locals.synd_result = synd_result
+    paged_search.push_pager(ctx, ctx.locals.synd_result)
+    ctx.add_session_vars('synd_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('synd_result')
+
+def page_display(ctx):
+#    try:
+#        query = globals.db.query('syndrome_types')
+#        ctx.locals.syndromes = query.fetchall()
+#    except dbobj.DatabaseError, e:
+#        ctx.add_error(e)
+    ctx.locals.all_synd = syndrome.syndromes.all()
+    groups_pt = globals.db.participation_table('group_syndromes', 
+                                               'syndrome_id', 'group_id')
+    synd_ids = [pkey[0] for pkey in ctx.locals.synd_result.page_pkeys()]
+    groups_pt.preload(synd_ids)
+    groups_pt.get_slave_cache().preload_all()
+    ctx.locals.groups_pt = groups_pt
+    ctx.locals.page = [syndrome.syndromes[id] for id in synd_ids]
+    ctx.run_template('admin_syndromes.html')
diff --git a/pages/admin_unit.html b/pages/admin_unit.html
new file mode 100644
index 0000000..d9d71a5
--- /dev/null
+++ b/pages/admin_unit.html
@@ -0,0 +1,192 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="group_edit.html" />
+
+<al-macro name="contact_user_select">
+ <table border="0" class="darker">
+  <al-if expr="contact_user_select.results is None">
+   <al-if expr="unit.contact_user_id is not None">
+    <al-exec expr="contact_user = unit.get_ref('contact_user_id')" />
+    <tr>
+     <td>Name:</td>
+     <td><al-value expr="contact_user.fullname" />
+    </tr>
+    <tr>
+     <td>Title:</td>
+     <td><al-value expr="contact_user.title" />
+    </tr>
+    <tr>
+     <td>Work Phone:</td>
+     <td><al-value expr="contact_user.phone_work" />
+    </tr>
+    <tr>
+     <td>Home Phone:</td>
+     <td><al-value expr="contact_user.phone_home" />
+    </tr>
+    <tr>
+     <td>Mobile Phone:</td>
+     <td><al-value expr="contact_user.phone_mobile" />
+    </tr>
+    <tr>
+     <td>Fax Phone:</td>
+     <td><al-value expr="contact_user.phone_fax" />
+    </tr>
+    <tr>
+     <td>e-mail:</td>
+     <td><al-value expr="contact_user.email" />
+    </tr>
+   </al-if>
+   <tr>
+    <td colspan="2">
+     <al-input name="contact_user_select.search_term" size="40" />
+     <al-input type="submit" class="butt" name="contact_user_select_go" value="Search" />
+    </td>
+   </tr>
+   <al-if expr="contact_user_select.error_msg">
+    <tr>
+     <td class="reverr" colspan="2">
+      <al-value expr="contact_user_select.error_msg" />
+     </td>
+    </tr>
+   </al-if>
+  <al-else>
+   <tr><th colspan="3">User search results:</th></tr>
+   <al-for iter="result_i" expr="contact_user_select.results">
+    <al-exec expr="user_result = result_i.value()" />
+    <tr>
+     <td><al-value expr="user_result.username"></td>
+     <td><al-value expr="user_result.fullname"></td>
+     <td align="right"><al-input type="submit" class="butt" value="Apply"
+       nameexpr="'contact:%s' % user_result.user_id" /></td>
+    </tr>
+   </al-for>
+   <tr>
+    <td class="darkest" align="right" colspan="3">
+     <al-input type="submit" class="butt" name="contact_user_select_cancel" value="Cancel" />
+    </td>
+   </tr>
+  </al-if>
+ </table>
+</al-macro>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.unit_label" /> <al-value expr="repr(unit.name)" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <td colspan="2">
+     <table width="100%" border="0">
+      <tr>
+       <th width="100%">Edit <al-value expr="config.unit_label"></th>
+       <al-if expr="not unit.is_new()">
+        <td align="center" class="darkest">
+          <al-input name="users" type="submit" class="butt" value="Users" />
+        </td>
+       </al-if>
+      </tr>
+     </table>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Name:</td>
+    <td><al-input name="unit.name" size="60" /></td>
+   </tr>
+   <tr>
+    <td align="right">Enabled:</td>
+    <td align="left">
+     <al-input id="unit_enabled" name="unit.enabled" type="radio" value="True">Yes
+     <al-input id="unit_disabled" name="unit.enabled" type="radio" value="">No
+    </td>
+   </tr>
+   <tr>
+    <td align="right"><al-value expr="config.group_label" />:</td>
+    <td>
+     <al-expand name="group_edit" />
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Rights:</td>
+    <td align="left">
+     <al-if expr="group_pt">
+      <table width="100%" class="gridtab" cellpadding="2" style="text-align: center;">
+       <tr>
+        <al-for iter="g_i" expr="group_pt">
+         <th><al-value expr="g_i.value().group_name" /></th>
+        </al-for>
+        <th><al-value expr="config.unit_label" /></th>
+        <th style="text-align: left;" width="100%">Right</th>
+       </tr>
+       <al-for iter="r_i" expr="credentials.Rights.available">
+        <tr class="darker">
+         <al-for iter="g_i" expr="group_pt">
+          <td>
+           <al-input type="checkbox" name="grouprights" disabled
+             checkedbool="r_i.value().right in credentials.Rights(g_i.value().rights)" />
+          </td>
+         </al-for>
+         <td>
+          <al-input type="checkbox" name="urights" list
+           valueexpr="r_i.value().right" />
+         </td>
+         <td align="left">
+          <al-value expr="r_i.value().label" />
+          <al-if expr="r_i.value().desc">
+           <span class="smaller"> (<al-value expr="r_i.value().desc"/>)</span>
+          </al-if>
+         </td>
+        </tr>
+       </al-for>
+      </table>
+     </al-if>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Street Address:</td>
+    <td><al-input name="unit.street_address" size="60" /></td>
+   </tr>
+   <tr>
+    <td align="right">Postal Address:</td>
+    <td><al-input name="unit.postal_address" size="60" /></td>
+   </tr>
+   <tr>
+    <td align="right">Contact Person:</td>
+    <td>
+      <al-expand name="contact_user_select" />
+    </td>
+   </tr>
+   <tr>
+    <td colspan="2" class="darkest">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="center" width="100%">
+         <al-input class="butt danger" name="delete" type="submit" value="delete" />
+        </td>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   </tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_unit.py b/pages/admin_unit.py
new file mode 100644
index 0000000..d3265d5
--- /dev/null
+++ b/pages/admin_unit.py
@@ -0,0 +1,122 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from cocklebur.foreign_key import ForeignKeySearch
+from cocklebur.pt import SelectPT
+from casemgr import globals, paged_search, credentials
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        ctx.locals.unit.rights = str(credentials.Rights(ctx.locals.urights))
+        if ctx.locals.group_edit.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.unit.db_has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        unit = ctx.locals.unit
+        if unit.name:
+            unit.name = unit.name.strip()
+        if not unit.name:
+            raise page_common.PageError('A unit name must be given')
+        unit.rights = str(credentials.Rights(ctx.locals.urights))
+        ctx.admin_log(unit.db_desc())
+        unit.db_update()
+        ctx.locals.group_edit.set_key(unit.unit_id)
+        ctx.admin_log(ctx.locals.group_edit.db_desc())
+        ctx.locals.group_edit.db_update()
+        globals.db.commit()
+        ctx.add_message('Updated %s %r' % (config.unit_label.lower(), unit.name))
+        globals.notify.notify('units', unit.unit_id)
+        globals.notify.notify('unit_groups', unit.unit_id)
+        globals.notify.notify('syndrome_units', unit.unit_id)
+
+    def revert(self, ctx):
+        ctx.locals.group_edit.db_revert()
+        ctx.locals.unit.db_revert()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_users(self, ctx, ignore):
+        prefs = ctx.locals._credentials.prefs
+        query = globals.db.query('users', order_by='username')
+        query.join('JOIN unit_users USING (user_id)')
+        query.where('unit_users.unit_id = %s', ctx.locals.unit.unit_id)
+        query.where('not users.deleted')
+        search = paged_search.SortablePagedSearch(globals.db, prefs, query)
+        ctx.push_page('admin_users', search)
+
+    def do_contact_user_select_go(self, ctx, ignore):
+        contact_user_select = ctx.locals.contact_user_select
+        contact_user_select.new_query()
+        contact_user_select.query.join('JOIN unit_users USING (user_id)')
+        contact_user_select.query.where('enabled = True')
+        contact_user_select.query.where('unit_users.unit_id = %s', 
+                                        ctx.locals.unit.unit_id)
+        contact_user_select.fetchall()
+
+    def do_contact_user_select_cancel(self, ctx, ignore):
+        ctx.locals.contact_user_select.reset()
+
+    def do_contact(self, ctx, user_id):
+        ctx.locals.unit.contact_user_id = int(user_id)
+        ctx.locals.contact_user_select.reset()
+
+    def do_delete(self, ctx, ignore):
+        credentials.delete_unit(ctx.locals.unit)
+        globals.db.commit()
+        ctx.add_message('Deleted unit %r' % ctx.locals.unit.name)
+        ctx.pop_page()
+
+    def do_group_edit(self, ctx, op, *args):
+        ctx.locals.group_edit.do(op, *args)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, unit_id):
+    if unit_id is None:
+        ctx.locals.unit = globals.db.new_row('units')
+    else:
+        query = globals.db.query('units')
+        query.where('unit_id = %s', unit_id)
+        ctx.locals.unit = query.fetchone()
+    ctx.locals.group_pt = globals.db.ptset('unit_groups', 
+                                           'unit_id', 'group_id', unit_id)
+    ctx.locals.group_pt.get_slave_cache().preload_all()
+    ctx.locals.group_edit = SelectPT(ctx.locals.group_pt, 'group_name')
+    ctx.locals.contact_user_select = ForeignKeySearch(ctx.locals.unit,
+                                                      'contact_user_id',
+                                                      ('username', 'fullname'))
+    ctx.locals.urights = list(credentials.Rights(ctx.locals.unit.rights))
+    ctx.add_session_vars('unit', 'group_pt', 'group_edit', 
+                         'contact_user_select', 'urights')
+
+def page_leave(ctx):
+    ctx.del_session_vars('unit', 'group_pt', 'group_edit', 
+                         'contact_user_select', 'urights')
+
+def page_display(ctx):
+    ctx.run_template('admin_unit.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_units.html b/pages/admin_units.html
new file mode 100644
index 0000000..6242274
--- /dev/null
+++ b/pages/admin_units.html
@@ -0,0 +1,95 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="config.unit_label" /></al-setarg>
+  <table border="0" class="gridtab">
+   <thead class="darkest">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+       <al-input class="smallbutt" name="add:" type="submit" value="New" />
+     </th>
+    </tr>
+   </thead>
+   <tfoot class="darkest">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <td colspan="7" align="left" class="darkest">
+      <table width="100%" cellspacing="0" cellpadding="0" border="0">
+       <tr>
+        <td align="left">
+         Select
+         <al-input name="select_all" class="smallbutt" 
+          type="submit" value="All" />
+         <al-input name="select_none" class="smallbutt" 
+          type="submit" value="None" whitespace />
+        </td>
+        <td align="center">
+         Selected <al-value expr="config.unit_label.lower()" /> to/from <al-value expr="config.group_label.lower()" />
+         <al-select name="select_group_id" optionexpr="option_groups" />
+         <al-input name="select_group:add" class="butt" 
+          type="submit" value="Add" />
+         <al-input name="select_group:del" class="butt" 
+          type="submit" value="Remove" />
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="units_i" expr="paged_search.result_page()">
+     <al-exec expr="unit = units_i.value()" />
+     <al-if expr="units_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <td>
+       <al-input name="selected" type="checkbox" 
+        valueexpr="unit.unit_id" list="list" />
+      </td>
+      <td><al-value expr="unit.name" /></td>
+      <td><al-value expr="unit.street_address" /></td>
+      <td><al-value expr="unit.enabled" lookup="boolean" /></td>
+      <td><al-value expr="groups_pt[unit.unit_id].comma_list('group_name')" /></td>
+      <td align="center" nowrap>
+       <al-input nameexpr="'edit:%s' % unit.unit_id" 
+            class="smallbutt" type="submit" value="Edit" />
+      </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="7" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_units.py b/pages/admin_units.py
new file mode 100644
index 0000000..8e96456
--- /dev/null
+++ b/pages/admin_units.py
@@ -0,0 +1,87 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add(self, ctx, ignore):
+        ctx.locals.unit_result.reset()
+        ctx.push_page('admin_unit', None)
+
+    def do_edit(self, ctx, unit_id):
+        ctx.locals.unit_result.reset()
+        ctx.push_page('admin_unit', int(unit_id))
+
+    def do_select_all(self, ctx, ignore):
+        ctx.locals.selected = unit_ids(ctx)
+
+    def do_select_none(self, ctx, ignore):
+        ctx.locals.selected = []
+
+    def do_select_group(self, ctx, cmd):
+        group_id = int(ctx.locals.select_group_id)
+        groups_pt = get_groups_pt(ctx)
+        group = groups_pt.get_slave_cache()[group_id]
+        for unit_id in ctx.locals.selected:
+            unit_id = int(unit_id)
+            if cmd == 'del':
+                groups_pt[unit_id].remove(group)
+            else:
+                groups_pt[unit_id].add(group)
+        groups_pt.db_update()
+        globals.db.commit()
+
+pageops = PageOps()
+
+def unit_ids(ctx):
+    return [pkey[0] for pkey in ctx.locals.unit_result.page_pkeys()]
+
+def get_groups_pt(ctx):
+    groups_pt = globals.db.participation_table('unit_groups', 
+                                               'unit_id', 'group_id')
+    groups_pt.preload(unit_ids(ctx))
+    groups_pt.get_slave_cache().preload_all()
+    return groups_pt
+
+def page_enter(ctx, unit_result):
+    unit_result.headers = [
+        (None, 'Selected'),
+        ('name', 'Name'),
+        ('street_address', 'Street Address'),
+        ('enabled', 'Enabled'),
+        (None, config.group_label),
+    ]
+    ctx.locals.unit_result = unit_result
+    paged_search.push_pager(ctx, ctx.locals.unit_result)
+    ctx.locals.selected = []
+    ctx.add_session_vars('unit_result', 'selected')
+ 
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('unit_result', 'selected')
+
+def page_display(ctx):
+    ctx.locals.groups_pt = get_groups_pt(ctx)
+    group_cache = ctx.locals.groups_pt.get_slave_cache()
+    ctx.locals.option_groups = group_cache.option_list('group_name')
+    ctx.run_template('admin_units.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/admin_user.html b/pages/admin_user.html
new file mode 100644
index 0000000..c097d25
--- /dev/null
+++ b/pages/admin_user.html
@@ -0,0 +1,193 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="ue.title" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <td colspan="2">
+     <table width="100%" cellpadding="0" cellspacing="0" border="0">
+      <tr>
+       <th width="100%">
+        <al-if expr="ue.user.deleted">View DELETED user<al-else>Edit user</al-if>
+       </th>
+       <al-if expr="not ue.user.is_new()">
+        <th align="right">
+         <al-input type="submit" class="butt" name="view_log" value="View Log" />
+        </th>
+       </al-if>
+      </tr>
+     </table>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Username:</td>
+    <td><al-input name="ue.user.username" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Full name:</td>
+    <td><al-input name="ue.user.fullname" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Sponsor:</td>
+    <td><al-if expr="ue.sponsor"><al-value expr="ue.sponsor.fullname" /> (<al-value expr="ue.sponsor.username" />)<al-else>None</al-if></td>
+   </tr>
+   <al-if expr="not ue.user.deleted">
+    <tr>
+     <td align="right">Enabled:</td>
+     <td align="left">
+      <al-input id="user_enabled" name="ue.user.enabled" type="radio" value="True">Yes 
+      <al-input id="user_disabled" name="ue.user.enabled" type="radio" value="">No
+     </td>
+    </tr>
+    <tr>
+     <td align="right">Locked due to bad password:</td>
+     <al-if expr="ue.lock_remain()">
+      <td bgcolor="#ffcccc">
+        <al-input class="right butt" type="submit" name="reset_attempts" value="Reset" />
+        Yes - <al-value expr="ue.lock_remain()" /> remain
+      </td>
+     <al-else>
+      <td>No</td>
+     </al-if>
+    </tr>
+    <tr>
+     <td align="right">Rights:</td>
+     <td align="left">
+      <table width="100%" class="gridtab" cellpadding="2" style="text-align: center;">
+       <tr>
+        <al-for iter="u_i" expr="units">
+         <th><al-value expr="u_i.value().name" /></th>
+        </al-for>
+        <th>User</th>
+        <th style="text-align: left;" width="100%">Right</th>
+       </tr>
+       <al-for iter="r_i" expr="credentials.Rights.available">
+        <tr class="darker">
+         <al-for iter="u_i" expr="units">
+          <td>
+           <al-input type="checkbox" name="unitrights" disabled
+             checkedbool="r_i.value().right in u_i.value().rights" />
+          </td>
+         </al-for>
+         <td>
+          <al-input type="checkbox" name="ue.rights" list
+           valueexpr="r_i.value().right" />
+         </td>
+         <td align="left">
+          <al-value expr="r_i.value().label" />
+          <al-if expr="r_i.value().desc">
+           <span class="smaller"> (<al-value expr="r_i.value().desc"/>)</span>
+          </al-if>
+         </td>
+        </tr>
+       </al-for>
+      </table>
+     </td>
+    </tr>
+    <tr>
+     <td align="right">New Password:</td>
+     <td><al-input type="password" name="ue.pwd.new_a" size="12" /></td>
+    </tr>
+    <tr>
+     <td align="right">Retype New Password:</td>
+     <td><al-input type="password" name="ue.pwd.new_b" size="12" /></td>
+    </tr>
+    <tr>
+     <td align="right"><al-value expr="config.unit_label" />:</td>
+     <td>
+      <al-expand name="search_pt">
+       <al-setarg name="left_title"><al-value expr="config.unit_label" /></al-setarg>
+       <al-setarg name="left_row"><al-value expr="row.name" /></al-setarg>
+       <al-setarg name="right_title">Add/Search</al-setarg>
+       <al-setarg name="right_row"><al-value expr="row.name" /></al-setarg>
+      </al-expand>
+     </td>
+    </tr>
+   </al-if>
+   <tr>
+    <td align="right">Job Title:</td>
+    <td><al-input name="ue.user.title" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Agency/Employer:</td>
+    <td><al-input name="ue.user.agency" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Expertise:</td>
+    <td><al-input name="ue.user.expertise" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">e-mail:</td>
+    <td><al-input name="ue.user.email" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Work Phone:</td>
+    <td><al-input name="ue.user.phone_work" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Mobile Phone:</td>
+    <td><al-input name="ue.user.phone_mobile" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Home phone:</td>
+    <td><al-input name="ue.user.phone_home" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td align="right">Fax:</td>
+    <td><al-input name="ue.user.phone_fax" size="40"
+      disabledbool="ue.user.deleted" /></td>
+   </tr>
+   <tr>
+    <td colspan="2" class="darkest">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="center" width="100%">
+         <al-if expr="ue.user.deleted">
+          <al-input class="butt danger" name="undelete" type="submit" value="undelete" />
+         <al-else>
+          <al-input class="butt danger" name="delete" type="submit" value="delete" />
+         </al-if>
+        </td>
+        <td align="right">
+         <al-if expr="not ue.user.deleted">
+          <al-input class="butt" name="update" type="submit" value="Save" />
+          <script>enterSubmit('appform', 'update');</script>
+         </al-if>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   <tr>
+  </table>
+</al-expand>
diff --git a/pages/admin_user.py b/pages/admin_user.py
new file mode 100644
index 0000000..159055a
--- /dev/null
+++ b/pages/admin_user.py
@@ -0,0 +1,94 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, pt
+from casemgr import globals, credentials, logview, unituser, user_edit
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.pt_search.db_has_changed():
+            raise page_common.ConfirmSave
+        if ctx.locals.ue.has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        user = ctx.locals.ue.user
+        if user.enabled and not ctx.locals.pt_search:
+            raise credentials.CredentialError('Cannot enable a user who is not '
+                                              'a member of at least one %s' % 
+                                              config.unit_label)
+        ctx.locals.ue.save()
+        ctx.add_messages(ctx.locals.ue.messages)
+        ctx.locals.pt_search.set_key(user.user_id)
+        ctx.admin_log(ctx.locals.pt_search.db_desc())
+        ctx.locals.pt_search.db_update()
+        globals.db.commit()
+        globals.notify.notify('users', user.user_id)
+
+    def do_update(self, ctx, ignore):
+        ctx.locals.pt_search.clear_search_result()
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_view_log(self, ctx, ignore):
+        user = ctx.locals.ue.user
+        if not user.is_new():
+            log = logview.AdminLogView(ctx.locals._credentials.prefs, 
+                                       'Log for user %r' % user.username, 
+                                       user_id=user.user_id)
+            ctx.push_page('logview', log)
+
+    def do_reset_attempts(self, ctx, ignore):
+        ctx.locals.ue.reset_attempts()
+
+    def do_delete(self, ctx, ignore):
+        orig_username = ctx.locals.ue.user.username
+        ctx.locals.ue.delete()
+        ctx.add_message('User %r deleted' % orig_username)
+
+    def do_undelete(self, ctx, ignore):
+        ctx.locals.ue.undelete()
+        ctx.add_message('User %r undeleted' % ctx.locals.ue.user.username)
+
+    def do_pt_search(self, ctx, op, *args):
+        ctx.locals.pt_search.do(op, *args)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, user_id):
+    ctx.locals.ue = user_edit.SystemAdmin(ctx.locals._credentials, user_id)
+    user_units = globals.db.ptset('unit_users', 'user_id', 'unit_id', user_id)
+    if (not user_units and hasattr(ctx.locals, 'unit') 
+        and ctx.locals.unit.unit_id):
+        user_units.add_slave_key(ctx.locals.unit.unit_id)
+    ctx.locals.pt_search = pt.SearchPT(user_units, 'name', filter='enabled')
+    ctx.add_session_vars('ue', 'pt_search')
+
+def page_display(ctx):
+    ids = [u.unit_id for u in ctx.locals.pt_search.pt_set]
+    ctx.locals.units = unituser.units.fetch(*ids)
+    ctx.run_template('admin_user.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
+
+def page_leave(ctx):
+    ctx.del_session_vars('ue', 'pt_search')
diff --git a/pages/admin_users.html b/pages/admin_users.html
new file mode 100644
index 0000000..a2751d5
--- /dev/null
+++ b/pages/admin_users.html
@@ -0,0 +1,71 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title"><al-value expr="title" /></al-setarg>
+  <table border="0" class="gridtab">
+   <thead class="darkest">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="6"><al-expand name="page_select" /></td></tr>
+    </al-if>
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+       <al-input class="butt" name="add:" type="submit" value="New User" />
+     </th>
+    </tr>
+   </thead>
+   <al-if expr="paged_search.has_pages()">
+    <tfoot class="darkest">
+     <tr><td colspan="6"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+   </al-if>
+   <tbody>
+    <al-for iter="users_i" expr="paged_search.result_page()">
+     <al-exec expr="user = users_i.value()" />
+     <al-if expr="users_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <td><al-value expr="user.enabled" lookup="boolean" /></td>
+      <td><al-value expr="user.username" /></td>
+      <td><al-value expr="user.fullname" /></td>
+      <td><al-value expr="user.title" /></td>
+      <td><al-value expr="users_units[user.user_id].comma_list('name')" /></td>
+      <td align="center" nowrap>
+       <al-input nameexpr="'edit:%s' % user.user_id" 
+            class="smallbutt" type="submit" value="Edit" />
+       <al-input nameexpr="'showlogs:%s' % user.user_id" 
+            class="smallbutt" type="submit" value="Logs" />
+      </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="6" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_users.py b/pages/admin_users.py
new file mode 100644
index 0000000..6c9ebd7
--- /dev/null
+++ b/pages/admin_users.py
@@ -0,0 +1,73 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search, logview
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add(self, ctx, ignore):
+        ctx.locals.user_result.reset()
+        ctx.push_page('admin_user', None)
+
+    def do_edit(self, ctx, user_id):
+        ctx.locals.user_result.reset()
+        ctx.push_page('admin_user', int(user_id))
+
+    def do_showlogs(self, ctx, user_id):
+        query = globals.db.query('users')
+        query.where('user_id = %s', user_id)
+        username = query.fetchone().username
+        log = logview.AdminLogView(ctx.locals._credentials.prefs, 
+                                    'Log for user %r' % username, 
+                                    user_id=user_id)
+        ctx.push_page('logview', log)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, user_result):
+    user_result.headers = [
+        ('enabled', 'Enabled'),
+        ('username', 'Username'),
+        ('fullname', 'Full Name'),
+        ('title', 'Title'),
+        (None, config.unit_label),
+    ]
+    ctx.locals.user_result = user_result
+    paged_search.push_pager(ctx, ctx.locals.user_result)
+    ctx.add_session_vars('user_result')
+ 
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('user_result')
+
+def page_display(ctx):
+    user_ids = [pkey[0] for pkey in ctx.locals.user_result.page_pkeys()]
+    ctx.locals.users_units = globals.db.participation_table('unit_users',
+                                                            'user_id', 
+                                                            'unit_id')
+    ctx.locals.users_units.preload(user_ids)
+    ctx.locals.title = 'Users'
+    if hasattr(ctx.locals, 'unit'):
+        ctx.locals.title += ' for %s %s' % (config.unit_label, ctx.locals.unit.name)
+    ctx.run_template('admin_users.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/admin_view_right.html b/pages/admin_view_right.html
new file mode 100644
index 0000000..573fe95
--- /dev/null
+++ b/pages/admin_view_right.html
@@ -0,0 +1,127 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="avr_user">
+    <tr>
+     <td width="20"></td>
+     <td width="20"></td>
+     <td width="20"></td>
+     <td><al-value expr="user.name" /></td>
+     <td><al-value expr="user.fullname" /></td>
+     <td style="text-align: right">
+      <al-input nameexpr="'edit_user:%s' % user.id" 
+            class="smallbutt" type="submit" value="Edit" /></td>
+    </tr>
+</al-macro>
+
+<al-macro name="avr_unit">
+     <tr>
+      <td width="20"></td>
+      <td width="20"></td>
+      <th colspan="3">
+       <al-value expr="unit.name" /> <al-value expr="config.unit_label" />
+      </th>
+      <th style="text-align: right;">
+       <al-input nameexpr="'edit_unit:%s' % unit.id" 
+             class="smallbutt" type="submit" value="Edit" /></th>
+     </tr>
+     <al-if expr="unit.users">
+      <al-for vars="user" expr="unit.sorted_users()">
+       <al-expand name="avr_user" />
+      </al-for>
+     <al-else>
+      <tr>
+       <td width="20"></td>
+       <td width="20"></td>
+       <td width="20"></td>
+       <td colspan="3"><i>nil</i></td>
+      </tr>
+     </al-if>
+</al-macro>
+
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">Right <al-value expr="rm.right" /> - <al-value expr="rm.label" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <th colspan="6">Via <al-value expr="config.group_label" /></th>
+   </tr>
+   <al-if expr="rm.direct_groups">
+    <al-for vars="group" expr="rm.sorted_groups()">
+     <tr>
+      <td width="20"></td>
+      <th colspan="4">
+       <al-value expr="group.name" /> <al-value expr="config.group_label" />
+      </th>
+      <th style="text-align: right;">
+       <al-input nameexpr="'edit_group:%s' % group.id" 
+             class="smallbutt" type="submit" value="Edit" /></th>
+     </tr>
+     <al-if expr="group.units">
+      <al-for vars="unit" expr="group.sorted_units()">
+       <al-expand name="avr_unit" />
+      </al-for>
+     <al-else>
+      <tr>
+       <td width="20"></td>
+       <td width="20"></td>
+       <td colspan="4">
+        <i>nil</i>
+       </td>
+      </tr>
+     </al-if>
+    </al-for>
+   <al-else>
+    <tr>
+     <td width="20"></td>
+     <td colspan="5"><i>nil</i></td>
+    </tr>
+   </al-if>
+
+   <tr>
+    <th colspan="6">Via <al-value expr="config.unit_label" /></th>
+   </tr>
+   <al-if expr="rm.direct_units">
+    <al-for vars="unit" expr="rm.sorted_units()">
+      <al-expand name="avr_unit" />
+    </al-for>
+   <al-else>
+    <tr>
+     <td width="20"></td>
+     <td colspan="5"><i>nil</i></td>
+    </tr>
+   </al-if>
+
+   <tr>
+    <th colspan="6">Via User</th>
+   </tr>
+   <al-if expr="rm.direct_users">
+    <al-for vars="user" expr="rm.sorted_users()">
+      <al-expand name="avr_user" />
+    </al-for>
+   <al-else>
+    <tr>
+     <td width="20"></td>
+     <td colspan="5"><i>nil</i></td>
+    </tr>
+   </al-if>
+  </table>
+</al-expand>
+
diff --git a/pages/admin_view_right.py b/pages/admin_view_right.py
new file mode 100644
index 0000000..c4bbafb
--- /dev/null
+++ b/pages/admin_view_right.py
@@ -0,0 +1,46 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import rights, globals
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_edit_user(self, ctx, user_id):
+        ctx.push_page('admin_user', int(user_id))
+
+    def do_edit_unit(self, ctx, unit_id):
+        ctx.push_page('admin_unit', int(unit_id))
+
+    def do_edit_group(self, ctx, group_id):
+        ctx.push_page('admin_group', int(group_id))
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, right):
+    ctx.locals.view_right = right
+    ctx.add_session_vars('view_right')
+
+def page_leave(ctx):
+    ctx.del_session_vars('view_right')
+
+def page_display(ctx):
+    ctx.locals.rm = rights.RightMembers(globals.db, ctx.locals.view_right)
+    ctx.run_template('admin_view_right.html')
diff --git a/pages/admin_wikiedit.html b/pages/admin_wikiedit.html
new file mode 100644
index 0000000..74af2d9
--- /dev/null
+++ b/pages/admin_wikiedit.html
@@ -0,0 +1,56 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_admin">
+ <al-setarg name="title">
+  <al-value expr="wikiedit.title" /> - <al-value expr="wikiedit.prompt" />
+ </al-setarg>
+ <al-table border="0" classexpr="wikiedit.style" width="95%">
+  <tr>
+   <td align="right" valign="top">
+    <label for="wikiedit.value"><al-value expr="wikiedit.prompt"></label>
+   </td>
+   <td>
+    <al-textarea name="wikiedit.value" cols="60" rows="10" />
+    <al-expand name="wikihelp" />
+   </td>
+  </tr>
+  <tr>
+   <td><al-input class="butt" name="refresh" type="submit" 
+      value="Refresh" /></td>
+   <td class="darker">
+    <al-value expr="wiki_text(wikiedit.value)" noescape="noescape" />
+   </td>
+  </tr>
+  <tr>
+   <td colspan="2">
+    <al-expand name="confirm_or_error">
+     <table width="100%" cellpadding="0" cellspacing="0" class="darker">
+      <tr>
+       <td align="right">
+        <al-input class="butt" name="update" type="submit" value="Save" />
+       </td>
+      </tr>
+     </table>
+    </al-expand>
+   </td>
+  </tr>
+ </al-table>
+</al-expand>
diff --git a/pages/admin_wikiedit.py b/pages/admin_wikiedit.py
new file mode 100644
index 0000000..a6fe826
--- /dev/null
+++ b/pages/admin_wikiedit.py
@@ -0,0 +1,64 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsLeaf):
+    def unsaved_check(self, ctx):
+        if ctx.locals.wikiedit.has_changed():
+            ctx.add_error('WARNING: %s changes discarded' % 
+                           ctx.locals.wikiedit.prompt)
+
+    def commit(self, ctx):
+        ctx.locals.wikiedit.update()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+pageops = PageOps()
+
+class WikiEdit:
+    def __init__(self, ns, field, prompt, title, style):
+        self.ns = ns
+        self.field = field
+        self.prompt = prompt
+        self.title = title
+        self.style = style
+        self.value = getattr(self.ns, self.field, '')
+
+    def has_changed(self):
+        return self.value != getattr(self.ns, self.field, '')
+
+    def update(self):
+        setattr(self.ns, self.field, self.value)
+
+def page_enter(ctx, *args):
+    ctx.locals.wikiedit = WikiEdit(*args)
+    ctx.add_session_vars('wikiedit')
+
+def page_leave(ctx):
+    ctx.del_session_vars('wikedit')
+
+def page_display(ctx):
+    ctx.run_template('admin_wikiedit.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/bulletin_detail.html b/pages/bulletin_detail.html
new file mode 100644
index 0000000..66c8d30
--- /dev/null
+++ b/pages/bulletin_detail.html
@@ -0,0 +1,50 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="bulletin_list.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Bulletin: <al-value expr="bulletin.title" /></al-setarg>
+ <table width="100%" cellspacing="0" cellpadding="0" border="0">
+  <tr>
+   <td valign="top" align="center">
+    <div class="bulletin-detail">
+     <table width="100%" border="0" />
+      <tr>
+       <th>Title:</th>
+       <td><al-value expr="bulletin.title" /></td>
+      </tr>
+      <tr>
+       <th>Posted:</th>
+       <td><al-value expr="bulletin.post_date" /></td>
+      </tr>
+      <tr>
+       <th></th>
+       <td><al-value expr="wiki_text(bulletin.detail)" noescape="noescape" /></td>
+      </tr>
+     </table>
+    </div>
+   </td>
+   <td width="20%" valign="top">
+    <al-expand name="bulletin_list" />
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/bulletin_detail.py b/pages/bulletin_detail.py
new file mode 100644
index 0000000..f3af7f8
--- /dev/null
+++ b/pages/bulletin_detail.py
@@ -0,0 +1,44 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_bulletin_detail(self, ctx, bulletin_id):
+        ctx.set_page('bulletin_detail', int(bulletin_id))
+
+    def do_back(self, ctx, ignore):
+        ctx.pop_page()
+
+pageops = PageOps()
+
+def page_enter(ctx, bulletin_id):
+    ctx.locals.bulletin = ctx.locals.bulletins.get_bulletin(bulletin_id)
+    ctx.add_session_vars('bulletin')
+
+def page_display(ctx):
+    hide_time = ctx.locals._credentials.prefs.get('bulletin_time')
+    ctx.locals.bulletin_list = ctx.locals.bulletins.get_bulletins(hide_time)
+    ctx.run_template('bulletin_detail.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
+
+def page_leave(ctx):
+    ctx.del_session_vars('bulletin')
diff --git a/pages/bulletin_list.html b/pages/bulletin_list.html
new file mode 100644
index 0000000..ec2f66a
--- /dev/null
+++ b/pages/bulletin_list.html
@@ -0,0 +1,47 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="bulletin_list">
+ <div class="bulletin-list">
+   <al-for vars="bulletin" iter="bulletin_i" expr="bulletin_list">
+    <div class="item">
+     <h1><al-value expr="bulletin.title" /></h1>
+     <div class="synopsis">
+      <al-value expr="bulletin.synopsis" />
+      <al-if expr="bulletin.detail">
+       <span class="more">
+        <al-if expr="has_js == 'yes'">
+         <a href="javascript:linksubmit('appform', 'bulletin_detail', <al-value expr='bulletin.bulletin_id' />)">More»</a>
+        <al-else>
+         <al-input nameexpr="'bulletin_detail:%d' % bulletin.bulletin_id" 
+          type="submit" value="More" />
+        </al-if>
+       </span>
+      </al-if>
+      </div>
+     <al-if expr="bulletin.post_date">
+      <div class="date">
+       Posted <al-value expr="bulletin.post_date.date()" />
+      </div>
+     </al-if>
+    </div>
+   </al-for>
+ </div>
+</al-macro>
diff --git a/pages/case.html b/pages/case.html
new file mode 100644
index 0000000..8996fe7
--- /dev/null
+++ b/pages/case.html
@@ -0,0 +1,59 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+<al-include name="taskbanner.html" />
+<al-include name="form.html" />
+<al-include name="tabs.html" />
+
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="case.title()" /></al-setarg>
+ <al-expand name="taskbanner" />
+ <al-expand name="caseset" />
+
+ <al-if expr="not case.deleted and case.person.data_src">
+  <div class="redbox">
+   The demographics for this record are maintained via 
+   [<al-value expr="case.person.data_src" />] and have been marked for
+   viewing only.
+   <al-input name="ownsrc" type="submit" class="bigbutt" 
+     value="Take Ownership" />
+  </div>
+ </al-if>
+ <al-exec expr="render_fields = case.get_demog_fields('case')" />
+ <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields" />
+ </div>
+ <al-if expr="case.deleted">
+  <div class="redbox">
+   This record was deleted <al-if expr="case.delete_reason">
+    (<al-value expr="case.delete_reason" />)
+   </al-if><al-if expr="case.delete_timestamp"> on <al-value expr="case.delete_timestamp" /></al-if>
+  </div>
+ </al-if>
+
+ <al-expand name="confirm_or_error">
+  <al-expand name="menubar" />
+ </al-expand>
+ <script>enterSubmit('appform', 'update');</script>
+
+ <al-expand name="form_summary" />
+</al-expand>
diff --git a/pages/case.py b/pages/case.py
new file mode 100644
index 0000000..a22ba94
--- /dev/null
+++ b/pages/case.py
@@ -0,0 +1,234 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, form_ui
+from casemgr import globals, tasks, casemerge, logview, caseset, casetags
+from pages import page_common, caseset_ops
+import config
+
+class ConfirmCaseDelete(page_common.ConfirmDelete):
+    mode = 'delete'
+    title = 'You are deleting this case'
+    reason_prompt = 'Reason for deletion:'
+
+
+class ConfirmCaseUndelete(page_common.ConfirmUndelete):
+    mode = 'delete'
+    title = 'You are undeleting this case'
+
+
+def desc_task(ctx):
+    action = tasks.ACTION_UPDATE_CASE
+    if ctx.locals.case.is_new():
+        action = tasks.ACTION_NEW_CASE
+    return dict(case_id = ctx.locals.case.case_row.case_id,
+                action = action)
+
+class PageOps(page_common.PageOpsBase, caseset_ops.CasesetOps):
+
+    def unsaved_check(self, ctx):
+        if ctx.locals.case.has_changed():
+            raise page_common.ConfirmSave
+    
+    def rollback(self, ctx):
+        ctx.locals.case.revert()
+
+    def commit(self, ctx):
+        case = ctx.locals.case
+        try:
+            log = case.db_desc()
+            task_desc = desc_task(ctx)
+            case.update()
+            case.user_log(log)
+            page_common.task_update(ctx, task_desc)
+            globals.db.commit()
+            ctx.add_message('Case updated')
+        except casetags.InvalidTags, e:
+            ctx.msg('err', e)
+            ctx.push_page('tagbrowse', 'Case tags', 'case.tags.cur')
+        except dbobj.DuplicateKeyError, e:
+            raise dbobj.DatabaseError('Case already added')
+
+    def do_back(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        self.rollback(ctx)
+        page_common.unlock_desc_task(ctx, desc_task(ctx))
+        ctx.pop_page()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        if ctx.locals.task and ctx.locals.task.done:
+            ctx.set_page('casetask', ctx.locals.case)
+
+    def do_edit(self, ctx, form_label, summary_id):
+        self.check_unsaved_or_confirmed(ctx)
+        edit_form = ctx.locals.case.edit_form(int(summary_id))
+        ctx.push_page('caseform', edit_form)
+
+    def do_new(self, ctx, form_label):
+        self.check_unsaved_or_confirmed(ctx)
+        edit_form = ctx.locals.case.new_form(form_label)
+        ctx.push_page('caseform', edit_form)
+
+    def do_useperson(self, ctx, person_id):
+        ctx.locals.case.use_person_id(int(person_id))
+        self.update_case(ctx, db)
+
+    def do_contacts(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.locals.case.invalidate_contact_count()
+        ctx.push_page('casecontacts')
+
+    def do_exposures(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.locals.case.invalidate_contact_count()
+        ctx.push_page('caseexposures')
+
+    def do_newtask(self, ctx, ignore):
+        ctx.push_page('casetask', ctx.locals.case)
+
+    def do_casetasks(self, ctx, ignore):
+        ctx.push_page('casetasks', ctx.locals.case)
+
+    def do_quit_task(self, ctx, ignore):
+        page_common.unlock_task(ctx)
+
+    def do_access(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.push_page('caseaccess')
+
+    def do_log(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        case_id = ctx.locals.case.case_row.case_id
+        log = logview.CaseLogView(ctx.locals._credentials.prefs, 
+                                  'Log for Case ID %r' % case_id,
+                                  case_id=case_id)
+        ctx.push_page('logview', log)
+
+    def do_delete(self, ctx, ignore):
+        if self.confirmed:
+            case = ctx.locals.case
+            case.set_deleted(True, ctx.locals.confirm.reason)
+            case.user_log('Deleted case')
+            globals.db.commit()
+        else:
+            raise ConfirmCaseDelete
+
+    def do_undelete(self, ctx, ignore):
+        if self.confirmed:
+            case = ctx.locals.case
+            case.set_deleted(False)
+            case.user_log('Undeleted case')
+            globals.db.commit()
+        else:
+            raise ConfirmCaseUndelete
+
+    def do_print(self, ctx, ignore):
+        case = ctx.locals.case
+        if case.case_row.is_new() or case.has_changed():
+            ctx.add_error('Save record before printing')
+        else:
+            ctx.push_page('caseprint')
+
+    def do_mergecase(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        selmerge = casemerge.by_case(ctx.locals._credentials,
+                                     ctx.locals.case.case_row,
+                                     ctx.locals.case.person)
+        ctx.push_page('selmergecase', selmerge)
+
+    def do_mergeforms(self, ctx, ignore):
+        if not ctx.locals.case.can_merge_forms():
+            raise page_common.PageError('There are no forms eligible to '
+                                        'be merged')
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.push_page('selmergeforms', ctx.locals.case)
+
+    def do_tab(self, ctx, tab):
+        ctx.locals.case.tabs.select(tab)
+
+    def do_ownsrc(self, ctx, ignore):
+        if self.confirmed:
+            ctx.locals.case.person.data_src = None
+            self.commit(ctx)
+        else:
+            raise page_common.ConfirmUnlock
+
+    def do_tagbrowse(self, ctx, ignore):
+        ctx.push_page('tagbrowse', 'Case tags', 'case.tags.cur')
+
+    def get_menubar(self, ctx):
+        menubar = page_common.MenuBar()
+        rw = 'VIEWONLY' not in ctx.locals._credentials.rights
+        if not ctx.locals.case.case_row.is_new():
+            menubar.add_middle('contacts', config.contact_label + 's')
+            cs_options = ctx.locals.casesets.caseoptions(ctx.locals.caseset)
+            droplist = menubar.add_middle('menubardrop', '(More actions)')
+            for cmd, label in cs_options:
+                droplist.add_drop(cmd, label)
+            droplist.add_droprule()
+            if ctx.locals.task:
+                menubar.add_middle('quit_task', 'Quit Task')
+            else:
+                if (not ctx.locals.case.deleted 
+                    and 'TASKINIT' in ctx.locals._credentials.rights):
+                    droplist.add_drop('newtask', 'Create a New Task')
+                droplist.add_drop('casetasks', 'Case Tasks')
+                droplist.add_droprule()
+            if rw:
+                droplist.add_drop('access', 'Access Control')
+            droplist.add_drop('caseset_person', 'Other records for this person')
+            droplist.add_drop('log', 'Activity Log')
+            droplist.add_droprule()
+            if 'mergeperson' not in ctx.locals.__pages__:
+                droplist.add_drop('mergecase', 'Merge Cases')
+            droplist.add_drop('mergeforms', 'Merge Forms')
+            droplist.add_droprule()
+            if ctx.locals.case.deleted and rw:
+                droplist.add_drop('undelete', 'Undelete', 
+                                    style='danger butt')
+            elif rw:
+                droplist.add_drop('delete', 'Delete', style='danger butt')
+                menubar.add_right('update', 'Update')
+        else:
+            menubar.add_right('update', 'Create')
+        menubar.done()
+        return menubar
+
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    task = ctx.locals.task 
+    case = ctx.locals.case
+    if task and task.case_id != case.case_row.case_id:
+        # This should not happen.
+        page_common.unlock_task(ctx)
+
+def page_leave(ctx):
+    ctx.locals.case = None
+
+def page_display(ctx):
+    ctx.locals.case.forms.refresh()
+    ctx.locals.menubar = pageops.get_menubar(ctx)
+    ctx.run_template('case.html')
+
+def page_process(ctx):
+    if ctx.locals.case.case_row.case_id:
+        ctx.locals._credentials.prefs.set_recent_case(ctx.locals.case.case_row.case_id, str(ctx.locals.case))
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/caseaccess.html b/pages/caseaccess.html
new file mode 100644
index 0000000..4e3b49a
--- /dev/null
+++ b/pages/caseaccess.html
@@ -0,0 +1,45 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+<al-include name="taskbanner.html" />
+<al-include name="search_pt.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Case Access</al-setarg>
+ <al-expand name="taskbanner" />
+ <al-exec expr="field_rows = case.rows_and_cols('form')" />
+ <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+ <al-expand name="search_pt">
+  <al-setarg name="left_title"><al-value expr="config.unit_label"> Access</al-setarg>
+  <al-setarg name="right_title"><al-value expr="config.unit_label"> Add/Search</al-setarg>
+ </al-expand>
+ <al-expand name="confirm_or_error">
+  <table width="100%" cellspacing="0" border="0">
+   <tr>
+    <td align="right">
+     <al-input type="submit" name="update" class="butt" value="Save" />
+    </td>
+   </tr>
+  </table>
+ </al-expand>
+</al-expand>
diff --git a/pages/caseaccess.py b/pages/caseaccess.py
new file mode 100644
index 0000000..4883a3a
--- /dev/null
+++ b/pages/caseaccess.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def unsaved_check(self, ctx):
+        if ctx.locals.case.acl.db_has_changed():
+            raise page_common.ConfirmSave
+
+    def commit(self, ctx):
+        log = ctx.locals.case.acl.db_desc()
+        ctx.locals.case.user_log(log)
+        ctx.locals.case.acl.db_update()
+        globals.db.commit()
+        if log:
+            ctx.add_message('Case access controls updated')
+
+    def revert(self, ctx):
+        ctx.locals.case.acl.db_revert()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_pt_search(self, ctx, op, *args):
+        if op == 'info':
+            unit_id = ctx.locals.pt_search.pt_available[int(args[0])].unit_id
+            ctx.push_page('unitview', unit_id)
+        else:
+            ctx.locals.case.acl.do(op, *args)
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    ctx.locals.case.load_acl()
+    ctx.locals.pt_search = ctx.locals.case.acl
+    ctx.add_session_vars('pt_search')
+
+def page_leave(ctx):
+    if hasattr(ctx.locals, 'case'):
+        ctx.locals.case.unload_acl()
+    ctx.del_session_vars('pt_search')
+
+def page_display(ctx):
+    ctx.run_template('caseaccess.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/casecontacts.html b/pages/casecontacts.html
new file mode 100644
index 0000000..9623cca
--- /dev/null
+++ b/pages/casecontacts.html
@@ -0,0 +1,107 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+<al-include name="taskbanner.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Case <al-value expr="config.contact_label" />s</al-setarg>
+ <al-expand name="taskbanner" />
+ <h3>Details of the case to which <al-value expr="config.contact_label.lower()" />s relate:</h3>
+ <al-exec expr="field_rows = case.rows_and_cols('form')" />
+ <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <al-expand name="confirm_or_error">
+  <span>This case has <al-value expr="len(contacts)"> <al-value expr="config.contact_label.lower()" /><al-if expr="len(contacts) != 1">s</al-if>.</span>
+  <table width="100%">
+   <tr>
+    <td>
+     Order by:
+     <al-select name="contacts.order_by" onchange="submit();"
+       optionexpr="contacts.get_order_options()" />
+     <al-if expr="not has_js">
+      <al-input type="submit" class="butt" name="refetch" value="Update" />
+     </al-if>
+      Select:<al-input type="submit" class="smallbutt" 
+      name="page:select_all" value="All"><al-input type="submit" 
+      class="smallbutt" name="page:select_none" value="None">
+    </td>
+    <td align="right">
+     <al-if expr="syndromes">
+      <al-select name="contact_syndrome" optionexpr="syndromes.options()" />
+      <al-input class="bigbutt" type="submit" name="add_contacts" 
+        expr="'Add ' + config.contact_label" />
+     </al-if>
+    </td>
+   </tr>
+  </table>
+
+  <al-if expr="contacts.result_count()">
+   <table class="casecontacts">
+    <thead>
+     <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+     <tr>
+      <th></th>
+      <th>ID</th>
+      <th><al-value expr="config.syndrome_label" /></th>
+      <th>Status</th>
+      <th><al-value expr="config.person_label" /></th>
+      <th><al-value expr="config.contact_label" /> Type</th>
+      <th><al-value expr="config.contact_label" /> Date</th>
+     </tr>
+    </thead>
+    <tfoot>
+     <tr>
+      <td class="select"> </td>
+      <td colspan="5" class="select">
+       Selected:
+       <al-input class="bigbutt" type="submit" name="editcontacts" 
+         value="Change type" />
+       <al-input class="bigbutt" type="submit" name="makecaseset" 
+         value="As Case Set" />
+      </td>
+      <td class="select" align="right">
+       <al-input type="submit" class="bigbutt danger" value="Remove selected"
+         name="remove_selected" /></td>
+     </tr>
+     <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+    <al-for iter="contact_i" expr="contacts.result_page()">
+     <al-exec expr="c = contact_i.value()">
+     <tr class="row">
+      <td class="select">
+       <al-input type="checkbox" list name="contacts.page_selected" 
+        valueexpr="contact_i.index()" />
+      </td>
+      <td><al-value expr="c.case_id" /></td>
+      <td><al-value expr="c.syndrome_name" /></td>
+      <td><al-value expr="c.case_status" /></td>
+      <td><al-value expr="c.person.summary(contacts.order)" /></td>
+      <td><al-value expr="c.contact_type" /></td>
+      <td><al-value expr="c.contact_date" /></td>
+     </tr>
+    </al-for>
+   </table>
+  </al-if>
+ </al-expand>
+</al-expand>
+
diff --git a/pages/casecontacts.py b/pages/casecontacts.py
new file mode 100644
index 0000000..55db88e
--- /dev/null
+++ b/pages/casecontacts.py
@@ -0,0 +1,107 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj, utils
+from casemgr import globals, paged_search, contacts, caseset
+from pages import page_common, search_ops, caseset_ops
+import config
+
+class ConfirmRemove(page_common.Confirm):
+    mode = 'remove_selected'
+    title = 'Dissociate selected records?'
+    message = 'Dissociate selected records?'
+    buttons = [
+        ('continue', 'No'),
+        ('confirm', 'Yes'),
+    ]
+
+
+class PageOps(page_common.PageOpsBase):
+    
+    def do_remove_selected(self, ctx, ignore):
+        keys = ctx.locals.contacts.selected or ctx.locals.contacts.pkeys
+        case_ids = [k[0] for k in keys]
+        if len(case_ids) > 1 and not self.confirmed:
+            raise ConfirmRemove(message='Dissociate %d cases with ID %s?' % 
+                              (len(case_ids), ctx.locals.case.case_row.case_id))
+        contacts.dissociate_contacts(ctx.locals.case.case_row.case_id, case_ids)
+        case = ctx.locals.case
+        cs_name = '%d records dissocated from %s, %s (ID %s)' % (
+                len(case_ids), case.person.surname, 
+                case.person.given_names, case.case_row.case_id)
+        ctx.locals.casesets.use(ctx.locals._credentials, 
+                                caseset.CaseSet(case_ids, cs_name))
+        ctx.locals.contacts.select([])
+        ctx.locals.contacts.reset()
+        ctx.locals.case.user_log('Dissociated ID(s) %s' % 
+                                    utils.commalist(case_ids, 'and'))
+        globals.db.commit()
+
+    def do_makecaseset(self, ctx, id):
+        ctx.pop_page()
+        if ctx.locals.contacts.selected:
+            case_ids = ctx.locals.contacts.selected_case_ids()
+            caseset_ops.make_caseset(ctx, case_ids,
+                'Select %ss of ID %s' % (config.contact_label.lower(), 
+                                         ctx.locals.case.case_row.case_id))
+        else:
+            cs = caseset.ContactCaseSet(ctx.locals._credentials,
+                                        ctx.locals.case.case_row.case_id)
+            caseset_ops.use_caseset(ctx, cs)
+
+    def do_add_contacts(self, ctx, id):
+        ops = search_ops.AssocContactOps(ctx.locals._credentials,
+                                         int(ctx.locals.contact_syndrome),
+                                         ctx.locals.case,
+                                         ctx.locals.contacts)
+        ctx.locals.contacts.reset()
+        ctx.push_page('search', ops)
+
+    def do_editcontacts(self, ctx, ignore):
+        if ctx.locals.contacts.selected:
+            case_ids = ctx.locals.contacts.selected_case_ids()
+            ctx.push_page('casecontacts_assoc', 'Update', case_ids)
+
+pageops = PageOps()
+
+
+
+def page_enter(ctx):
+    ctx.locals.contacts = contacts.ContactSearch(ctx.locals._credentials.prefs, 
+                                               ctx.locals.case.case_row.case_id,
+                                               ctx.locals.case.deleted)
+    paged_search.push_pager(ctx, ctx.locals.contacts)
+    ctx.add_session_vars('contacts')
+
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('contacts')
+
+
+def page_display(ctx):
+    ctx.run_template('casecontacts.html')
+
+
+def page_process(ctx):
+    try:
+        ctx.locals.contacts.page_process(ctx)
+        if pageops.page_process(ctx):
+            return
+    except dbobj.DatabaseError, e:
+        ctx.add_error(e)
diff --git a/pages/casecontacts_assoc.html b/pages/casecontacts_assoc.html
new file mode 100644
index 0000000..9aa8fc5
--- /dev/null
+++ b/pages/casecontacts_assoc.html
@@ -0,0 +1,110 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+<al-include name="taskbanner.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Case association</al-setarg>
+ <al-expand name="taskbanner" />
+ <h3>Details of the case to which <al-value expr="config.contact_label.lower()" />s relate:</h3>
+ <al-exec expr="field_rows = case.rows_and_cols('form')" />
+ <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <al-expand name="confirm_or_error">
+  <h3>Nature of <al-value expr="config.contact_label.lower()" />:</h3>
+  <table class="labelform">
+   <tr>
+    <al-if expr="new_contact_type">
+     <td class="label"><label for="contact_type">New <al-value expr="config.contact_label.lower()" /> type</label></td>
+     <td class="field">
+      <div class="fieldandbutt">
+       <div class="andbutt">
+        <al-input type="submit" name="new_contact_type_cancel" value="Cancel" />
+       </div>
+       <div class="fieldand">
+        <al-input type="text" id="contact_type" name="contact_type" />
+       </div>
+      </div>
+     </td>
+    <al-else>
+     <td class="label"><label for="contact_type_id"><al-value expr="config.contact_label" /> type</label></td>
+     <td class="field">
+      <div class="fieldandbutt">
+       <div class="andbutt">
+        <al-input type="submit" name="new_contact_type" value="New" />
+       </div>
+       <div class="fieldand">
+        <al-select id="contact_type_id" name="assoc_contacts.contact_type_id" 
+         optionexpr="assoc_contacts.contact_types()" />
+       </div>
+      </div>
+     </td>
+    </al-if>
+    <td class="label"><label for="contact_date"><al-value expr="config.contact_label" /> date</label></td>
+    <td class="field" nowrap>
+     <al-input name="assoc_contacts.contact_date" id="contact_date"
+      calendarformatexpr="assoc_contacts.datetime_fmt" />
+    </td>
+   </tr>
+  </table>
+
+  <table class="menubar">
+   <tr>
+    <td class="mbr">
+     <al-input class="bigbutt" type="submit" name="okay" 
+      expr="'%s %ss' % (assoc_mode, config.contact_label)" />
+    </td>
+   </tr>
+  </table>
+
+  <h3><al-value expr="config.contact_label" />s to be added:</h3>
+  <table class="casecontacts">
+   <thead>
+    <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+    <tr>
+     <th>ID</th>
+     <th><al-value expr="config.syndrome_label" /></th>
+     <th>Status</th>
+     <th><al-value expr="config.person_label" /></th>
+     <th><al-value expr="config.contact_label" /> Type</th>
+     <th><al-value expr="config.contact_label" /> Date</th>
+    </tr>
+   </thead>
+   <tfoot>
+    <tr><td colspan="7"><al-expand name="page_select" /></td></tr>
+   </tfoot>
+   <al-for iter="contact_i" expr="assoc_contacts.result_page()">
+    <al-exec expr="c = contact_i.value()">
+    <tr class="row">
+     <td><al-value expr="c.case_id" /></td>
+     <td><al-value expr="c.syndrome_name" /></td>
+     <td><al-value expr="c.case_status" /></td>
+     <td><al-value expr="c.person.summary(contacts.order_by_cols())" /></td>
+     <td><al-value expr="c.contact_type" /></td>
+     <td><al-value expr="c.contact_date" /></td>
+    </tr>
+   </al-for>
+  </table>
+ </al-expand>
+</al-expand>
+
diff --git a/pages/casecontacts_assoc.py b/pages/casecontacts_assoc.py
new file mode 100644
index 0000000..7fc3ab1
--- /dev/null
+++ b/pages/casecontacts_assoc.py
@@ -0,0 +1,69 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import dbobj, utils, datetime
+from casemgr import globals, search, paged_search, cases, contacts
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    
+    def do_new_contact_type(self, ctx, ignore):
+        ctx.locals.contact_type = ''
+
+    def do_new_contact_type_cancel(self, ctx, ignore):
+        ctx.locals.new_contact_type = None
+
+    def do_okay(self, ctx, ignore):
+        if ctx.locals.new_contact_type:
+            ctx.locals.assoc_contacts.new_contact_type(ctx.locals.contact_type)
+        ctx.locals.assoc_contacts.associate()
+        ctx.locals.case.user_log(ctx.locals.assoc_contacts.log_msg())
+        globals.db.commit()
+        ctx.locals.new_contact_type = None
+        ctx.pop_page('casecontacts')
+
+
+pageops = PageOps()
+
+
+def page_enter(ctx, mode, assoc_ids):
+    ctx.locals.assoc_mode = mode
+    prefs = ctx.locals._credentials.prefs
+    case_id = ctx.locals.case.case_row.case_id
+    ctx.locals.assoc_contacts = contacts.AssocContacts(prefs, case_id, assoc_ids)
+    paged_search.push_pager(ctx, ctx.locals.assoc_contacts)
+    ctx.locals.new_contact_type = None
+    ctx.add_session_vars('assoc_mode', 'assoc_contacts', 'new_contact_type')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('assoc_mode', 'assoc_contacts', 'new_contact_type')
+
+def page_display(ctx):
+    ctx.run_template('casecontacts_assoc.html')
+
+def page_process(ctx):
+    try:
+        ctx.locals.assoc_contacts.page_process(ctx)
+        if pageops.page_process(ctx):
+            return
+    except dbobj.DatabaseError, e:
+        ctx.add_error(e)
+
diff --git a/pages/caseform.html b/pages/caseform.html
new file mode 100644
index 0000000..b761f7e
--- /dev/null
+++ b/pages/caseform.html
@@ -0,0 +1,57 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+<al-include name="taskbanner.html" />
+<al-include name="form.html">
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr='edit_form.name'></al-setarg>
+ <al-expand name="taskbanner" />
+ <al-exec expr="field_rows = case.rows_and_cols('form')" />
+ <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+ <al-expand name="confirm_or_error" />
+ <al-if expr="not confirm and not case.deleted and edit_form.foreign_src()">
+  <div class="redbox">
+   This form is maintained via 
+   [<al-value expr="edit_form.data_src" />] and has been marked for
+   viewing only.
+   <al-input name="ownsrc" type="submit" class="bigbutt" 
+     value="Take Ownership" />
+  </div>
+ </al-if>
+
+ <al-if expr="edit_form.deleted">
+  <div class="redbox" style="min-height: 3ex;">
+   This form was deleted <al-if expr="edit_form.delete_reason">
+    (<al-value expr="edit_form.delete_reason" />)
+   </al-if> on <al-value expr="edit_form.delete_timestamp" />
+   <al-input name="form_undelete" type="submit" class="bigbutt right"
+    value="Undelete Form" />
+  </div>
+ </al-if>
+ 
+ <div class="syndrome-form">
+  <al-exec expr="form = edit_form.get_form_ui()" />
+  <al-expand name="form_page" />
+ </div>
+</al-expand>
diff --git a/pages/caseform.py b/pages/caseform.py
new file mode 100644
index 0000000..f339c4b
--- /dev/null
+++ b/pages/caseform.py
@@ -0,0 +1,138 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, form_ui
+from casemgr import globals, tasks
+from pages import page_common
+
+import config
+
+class ConfirmFormDelete(page_common.ConfirmDelete):
+    mode = 'delete'
+    title = 'Delete form'
+    message = 'Are you sure you wish to delete this form?'
+    reason_prompt = 'Reason for deletion:'
+
+
+class ConfirmFormUndelete(page_common.ConfirmUndelete):
+    mode = 'undelete'
+    title = 'Undelete form'
+    message = 'Are you sure you wish to undelete this form?'
+
+
+def desc_task(ctx, is_new, **kwargs):
+    action = tasks.ACTION_UPDATE_CASE_FORM
+    if is_new:
+        action = tasks.ACTION_NEW_CASE_FORM
+    return dict(kwargs, action = action,
+                case_id = ctx.locals.case.case_row.case_id)
+
+class PageOps(page_common.PageOpsBase):
+    def validate(self, ctx):
+        ctx.locals.form_errors = ctx.locals.edit_form.validate()
+        if ctx.locals.form_errors:
+            raise page_common.PageError('There are errors in some fields -'
+                                        ' please fix them and try again')
+
+    def unsaved_check(self, ctx):
+        if ctx.locals.edit_form.has_changed():
+            raise page_common.ConfirmSave
+    
+    def commit(self, ctx):
+        self.validate(ctx)
+        ctx.locals.case.user_log(ctx.locals.edit_form.db_desc())
+        try:
+            form_desc = ctx.locals.edit_form.update()
+        except globals.ReviewForm:
+            ctx.locals.form_data = ctx.locals.edit_form.get_form_data()
+            raise
+        page_common.task_update(ctx, desc_task(ctx, **form_desc))
+        globals.db.commit()
+        ctx.locals.case.forms.cache_invalidate()
+
+    def rollback(self, ctx):
+        form_desc = ctx.locals.edit_form.abort()
+        if form_desc:
+            page_common.unlock_desc_task(ctx, desc_task(ctx, **form_desc))
+
+    def do_form_submit(self, ctx, ignore):
+        self.commit(ctx)
+        if ctx.locals.task and ctx.locals.task.done:
+            ctx.set_page('casetask', ctx.locals.case)
+        else:        
+            ctx.pop_page()
+
+    def do_form_cancel(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        self.rollback(ctx)
+        ctx.pop_page()
+
+    def do_form_delete(self, ctx, ignore):
+        if self.confirmed:
+            ctx.locals.edit_form.set_deleted(True, ctx.locals.confirm.reason)
+            ctx.locals.case.user_log('Deleted form')
+            globals.db.commit()
+            ctx.locals.case.forms.cache_invalidate()
+            ctx.pop_page()
+        else:
+            raise ConfirmFormDelete
+
+    def do_form_undelete(self, ctx, ignore):
+        if self.confirmed:
+            ctx.locals.edit_form.set_deleted(False)
+            ctx.locals.case.user_log('Undeleted form')
+            globals.db.commit()
+            ctx.locals.case.forms.cache_invalidate()
+        else:
+            raise ConfirmFormUndelete
+
+    def do_showhelp(self, ctx, input_name):
+        ctx.locals.showhelp = input_name.replace('_', '.')
+        ctx.locals.curnode = ctx.locals.showhelp
+
+    def do_print(self, ctx, ignore):
+        ctx.push_page('caseprint')
+    
+    def do_ownsrc(self, ctx, ignore):
+        if self.confirmed:
+            ctx.locals.edit_form.take_ownership()
+            ctx.locals.case.forms.cache_invalidate()
+        else:
+            raise page_common.ConfirmUnlock
+
+pageops = PageOps()
+
+def page_enter(ctx, edit_form):
+    ctx.locals.edit_form = edit_form
+    ctx.locals.form_data = ctx.locals.edit_form.get_form_data()
+    ctx.locals.form_errors = form_ui.FormErrors()
+    ctx.locals.curnode = ''
+    ctx.add_session_vars('edit_form', 'form_data', 'form_errors', 'curnode')
+
+def page_leave(ctx):
+    ctx.del_session_vars('edit_form', 'form_data', 'form_errors', 'curnode')
+
+def page_display(ctx):
+    if not hasattr(ctx.locals, 'showhelp'):
+        ctx.locals.showhelp = ''
+    ctx.locals.form_disabled = (ctx.locals.confirm or
+                                ctx.locals.case.viewonly() or 
+                                ctx.locals.edit_form.viewonly())
+    ctx.run_template('caseform.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/caseprint.html b/pages/caseprint.html
new file mode 100644
index 0000000..54a0240
--- /dev/null
+++ b/pages/caseprint.html
@@ -0,0 +1,247 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="caseprint_demog">
+   <table class="cpsection">
+    <tr>
+     <td class="heading" colspan="4"><al-usearg name="demogtitle" /></td>
+    </tr>
+    <al-for iter="field_row_i" expr="field_rows">
+     <tr>
+      <al-for iter="field_i" expr="field_row_i.value()">
+       <al-exec expr="field = field_i.value()" />
+       <td class="question"><al-value expr="field.label" />: </td>
+       <td class="inputs">
+        <al-lookup expr="field.render">
+         <al-item expr="'select'">
+          <al-exec expr="value = eval(field.field)" />
+          <al-value expr="dict(field.optionexpr()).get(value, value)" />
+         </al-item>
+         <al-item expr="'passport'">
+          <al-value expr="eval(field.field + 'number')" />,
+          <al-exec expr="value = eval(field.field + 'country')" />
+          <al-value expr="dict(field.optionexpr()).get(value, value)" />
+         </al-item>
+         <al-exec expr="value = eval(field.field)" />
+         <al-if expr="value">
+          <al-value expr="value" />
+         <al-else>
+          <div class="textbox" style="border-bottom: 1px solid black; height: 1.5em;"> </div>
+         </al-if>
+        </al-lookup>
+       </td>
+      </al-for>
+     </tr>
+    </al-for>
+   </table>
+</al-macro>
+
+<al-lookup name="printform_filled_inputs">
+
+ <!-- RadioList -->
+ <al-item expr="'RadioList'">
+  <tr><td colspan="2">
+   <al-value expr="input.outtrans(form_data)" />
+  </tr></td>
+ </al-item>
+
+ <!-- DropList -->
+ <al-item expr="'DropList'">
+  <tr><td colspan="2">
+   <al-value expr="input.outtrans(form_data)" />
+  </tr></td>
+ </al-item>
+
+ <!-- CheckBoxes -->
+ <al-item expr="'CheckBoxes'">
+  <tr><td colspan="2">
+   <al-value expr="input.outtrans(form_data)" />
+  </tr></td>
+ </al-item>
+
+ <!-- Default -->
+ <!-- TextInput -->
+ <!-- DateInput -->
+ <!-- TextArea -->
+ <tr><td colspan="2">
+  <al-exec expr="value = eval('form_data.' + input.get_column_name())" />
+  <al-if expr="value is not None">
+   <al-value expr="value" />
+  <al-else>
+   <div class="textbox" style="border-bottom: 1px solid black; height: 1.5em;"> </div>
+  </al-if>
+ </tr></td>
+
+</al-lookup>
+
+<al-macro name="caseprint_form">
+   <table class="cpsection" border="0" cellspacing="0" cellpadding="0">
+    <al-tree iter="tree_i" expr="form">
+     <al-exec expr="node = tree_i.value()" />
+     <al-lookup expr="node.render" whitespace>
+      <al-item expr="'Question'">
+       <al-if expr="not node.disabled">
+        <al-exec expr="inputs = node.get_inputs()" />
+        <tr>
+         <td class="question number"><al-value expr="node.label" /></td>
+         <al-if expr="inputs">
+          <td class="question">
+         <al-else>
+          <td class="question" colspan="2">
+         </al-if>
+          <al-for iter="skiptext_i" expr="node.skiptext()">
+           <div class="skiptext"><al-value expr="wiki_oneliner(skiptext_i.value())" noescape></div>
+          </al-for>
+          <al-value expr="wiki_text(node.text)" noescape="noescape" />
+          <al-if expr="node.help">
+           <hr width="80%">
+           <div class="info">
+             <al-value expr="wiki_text(node.help)" noescape="noescape" />
+           </div>
+          </al-if>
+         </td>
+         <al-if expr="inputs">
+          <td class="inputs">
+           <table width="100%" border="0" cellspacing="0">
+            <al-for iter="input_i" expr="inputs">
+             <al-exec expr="input = input_i.value()">
+             <!-- render input -->
+             <al-if expr="hasattr(input, 'pre_text') and input.pre_text">
+              <tr><td class="pretext" colspan="2">
+               <al-value expr="wiki_oneliner(input.pre_text)" noescape="noescape" />
+              </td></tr>
+             </al-if>
+             <al-value lookup="printform_filled_inputs" expr="input.render" whitespace />
+             <al-if expr="hasattr(input, 'post_text') and input.post_text">
+              <tr><td class="posttext" colspan="2">
+               <al-value expr="wiki_oneliner(input.post_text)" noescape="noescape" />
+              </td></tr>
+             </al-if>
+            </al-for>
+           </table>
+          </td>
+         </al-if>
+        </tr>
+       </al-if>
+      </al-item>
+
+      <!-- SubSection -->
+      <al-item expr="'SubSection'">
+       <tr>
+        <td class="subsection number">
+         <al-value expr="node.label" />
+        </td>
+        <td class="subsection" colspan="2" width="100%">
+         <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+        </td>
+       </tr>
+      </al-item>
+
+      <!-- Section -->
+      <al-item expr="'Section'">
+       <tr>
+        <td class="section number">
+         <al-value expr="node.label" />
+        </td>
+        <td class="section" colspan="2" width="100%">
+         <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+        </td>
+       </tr>
+      </al-item>
+
+      <!-- Form -->
+      <al-item expr="'Form'">
+       <tr>
+        <td colspan="3" class="heading">
+         <al-value expr="node.text" />
+         <al-if expr="form_data.summary_id">
+          - <al-value expr="form_ui.form_id(form_data.summary_id)" />
+         </al-if>
+         <al-if expr="node.version">
+          <div class="info">
+           (Version <al-value expr="node.version">)
+          </div>
+         </al-if>
+        </td>
+       </tr>
+      </al-item>
+
+     </al-lookup>
+    </al-tree>
+   </table>
+</al-macro>
+
+<al-macro name="printcontacts">
+ <table border="0" class="cpsection" cellspacing="0">
+  <thead>
+   <tr>
+    <td class="heading" colspan="4">Case <al-value expr="config.contact_label" />s</td>
+   </tr>
+   <tr>
+    <th align="middle">ID</th>
+    <th align="left"><al-value expr="config.syndrome_label" /></th>
+    <th align="left">Status</th>
+    <th align="left"><al-value expr="config.person_label" /></th>
+    <th align="left"><al-value expr="config.contact_label" /> Type</th>
+    <th align="left"><al-value expr="config.contact_label" /> Date</th>
+   </tr>
+  </thead>
+  <tbody>
+   <al-for iter="page_i" expr="xrange(contacts.pages())">
+    <al-exec expr="contacts.page = page_i.index() + 1" />
+    <al-for iter="contact_i" expr="contacts.page_rows()">
+     <al-exec expr="c = contact_i.value()">
+     <tr>
+      <td align="middle"><al-value expr="c.case_id" /></td>
+      <td><al-value expr="c.syndrome_name" /></td>
+      <td><al-value expr="c.case_status" /></td>
+      <td><al-value expr="c.person.summary(contacts.order_by)" /></td>
+      <td><al-value expr="c.contact_type" /></td>
+      <td><al-value expr="c.contact_date" /></td>
+     </tr>
+    </al-for>
+   </al-for>
+  </tbody>
+ </table>
+</al-macro>
+
+<al-expand name="print_layout">
+  <al-setarg name="title"></al-setarg>
+  <al-expand name="confidential" />
+  <al-exec expr="field_rows = case.rows_and_cols('case')" />
+  <al-expand name="caseprint_demog">
+   <al-setarg name="demogtitle">
+    Case Demographics<al-if expr="case.deleted"> (DELETED)</al-if>
+   </al-setarg>
+  </al-expand>
+  <al-if expr="contacts">
+   <al-expand name="printcontacts" />
+  </al-if>
+  <al-if expr="hasattr(__ctx__, 'edit_form')">
+   <al-exec expr="form = edit_form.get_form_ui()" />
+   <al-expand name="caseprint_form" />
+  <al-else>     
+   <al-for iter="forms_i" expr="case.enumerate_forms()">
+    <al-exec expr="form, form_data = forms_i.value()" />
+    <al-expand name="caseprint_form" />
+   </al-for>
+  </al-if>
+</al-expand>
+
diff --git a/pages/caseprint.py b/pages/caseprint.py
new file mode 100644
index 0000000..4ce559b
--- /dev/null
+++ b/pages/caseprint.py
@@ -0,0 +1,31 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from pages import page_common
+from cocklebur import datetime, form_ui
+from casemgr import globals, contacts
+
+import config
+
+def page_display(ctx):
+    ctx.locals.contacts = contacts.ContactSearch(
+                                        ctx.locals._credentials.prefs, 
+                                        ctx.locals.case.case_row.case_id,
+                                        ctx.locals.case.deleted)
+    ctx.run_template('caseprint.html')
+
+page_process = page_common.page_process
diff --git a/pages/caseset_ops.py b/pages/caseset_ops.py
new file mode 100644
index 0000000..711bc26
--- /dev/null
+++ b/pages/caseset_ops.py
@@ -0,0 +1,159 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+import config
+from cocklebur import pageops
+from cocklebur.pageops import Confirm, ConfirmSave
+from casemgr import globals, caseset, reports
+from pages import page_common
+
+
+def use_caseset(ctx, cs):
+    ctx.locals.caseset = ctx.locals.casesets.use(ctx.locals._credentials, cs)
+    if ctx.locals.case is not None:
+        try:
+            ctx.locals.caseset.seek_case(ctx.locals.case.case_row.case_id)
+        except ValueError:
+            pass
+    if ctx.locals.case is None or cs.cur() != ctx.locals.case.case_row.case_id:
+        case = ctx.locals.caseset.edit_cur(ctx.locals._credentials)
+        page_common.edit_case(ctx, case, push=True)
+
+
+def make_caseset(ctx, case_ids, name=None):
+    if case_ids:
+        use_caseset(ctx, caseset.CaseSet(case_ids, name))
+    else:
+        ctx.add_error('No matching records found')
+
+
+def person_caseset(ctx, case):
+    cs = caseset.PersonCaseSet(ctx.locals._credentials, case.case_row.person_id)
+    cs.seek_case(case.case_row.case_id)
+    if len(cs) > 1:
+        use_caseset(ctx, cs)
+    else:
+        ctx.add_error('No matching records found')
+
+
+def caseset_remove(ctx, case_id):
+    if ctx.locals.caseset is not None:
+        ctx.locals.caseset.remove(case_id)
+        if ctx.locals.caseset.caseset_id:
+            if not ctx.locals.caseset:
+                ctx.locals.casesets.delete(ctx.locals.caseset)
+            else:
+                ctx.locals.casesets.save(ctx.locals.caseset)
+            globals.db.commit()
+            ctx.msg('info', 'Caseset saved')
+        if not ctx.locals.caseset:
+            ctx.locals.caseset = None
+
+
+def caseset_seek(ctx, delta):
+    try:
+        ctx.locals.caseset.seek(delta)
+    except IndexError:
+        return
+    case = ctx.locals.caseset.edit_cur(ctx.locals._credentials)
+    page_common.edit_case(ctx, case)
+
+
+class CasesetOps(pageops.PageOpsBase):
+    """
+    Handle actions resulting from the blue "caseset" banner bar
+    """
+
+    def do_caseset_close(self, ctx, ignore):
+        ctx.locals.caseset = None
+
+    def do_caseset_remove(self, ctx, ignore):
+        if ctx.locals.case:
+            self.check_unsaved_or_confirmed(ctx)
+            caseset_remove(ctx, ctx.locals.case.case_row.case_id)
+            if ctx.locals.caseset is not None:
+                case = ctx.locals.caseset.edit_cur(ctx.locals._credentials)
+                page_common.edit_case(ctx, case)
+
+    def do_caseset_add(self, ctx, id):
+        if not ctx.locals.case:
+            return
+        case_id = ctx.locals.case.case_row.case_id
+        if not id:
+            cs = caseset.CaseSet()
+            cs.append(case_id)
+        else:
+            cs = ctx.locals.casesets.load(int(id))
+            if cs is None or cs.dynamic:
+                return
+            cs.append(case_id)
+            ctx.locals.casesets.save(cs)
+            globals.db.commit()
+        ctx.msg('info', 'Added ID %s to case set %r' % (case_id, cs.name))
+        if ctx.locals.caseset is None:
+            ctx.locals.caseset = cs
+
+    def do_caseset_seek(self, ctx, offset):
+        self.check_unsaved_or_confirmed(ctx)
+        caseset_seek(ctx, int(offset))
+    
+    def do_caseset_person(self, ctx, ignore):
+        if ctx.locals.case:
+            person_caseset(ctx, ctx.locals.case)
+
+    def do_casesets_action(self, ctx, action):
+        ctx.locals.casesets_action = None
+        args = action.split(':')
+        action = args.pop(0)
+        if action == 'save':
+            ctx.locals.casesets.save(ctx.locals.caseset)
+            globals.db.commit()
+            ctx.msg('info', 'Caseset saved')
+        elif action == 'delete':
+            ctx.locals.casesets.delete(ctx.locals.caseset)
+            globals.db.commit()
+            ctx.msg('info', 'Caseset deleted')
+        elif action == 'load':
+            if ctx.locals.case and ctx.locals.case.has_changed():
+                raise ConfirmSave
+            cs = ctx.locals.casesets.load(int(args[0]))
+            use_caseset(ctx, cs)
+        elif action == 'sort':
+            ctx.locals.caseset.sort_by(*args)
+        elif action == 'rename':
+            ctx.locals.casesets.new_name = ctx.locals.caseset.name
+        elif action == 'rename_okay':
+            ctx.locals.casesets.rename(ctx.locals.caseset, 
+                                       ctx.locals.casesets.new_name)
+            if ctx.locals.caseset.caseset_id is not None:
+                globals.db.commit()
+                ctx.msg('info', 'Caseset saved')
+            ctx.locals.casesets.new_name = None
+        elif action == 'rename_cancel':
+            ctx.locals.casesets.new_name = None
+        elif action == 'report':
+            self.check_unsaved_or_confirmed(ctx)
+            syndrome_id = ctx.locals.case.case_row.syndrome_id
+            ctx.pop_page('main')
+            ctx.push_page('report_menu', syndrome_id)
+            reportparams = reports.new_report(syndrome_id)
+            reportparams.label = ctx.locals.caseset.name
+            reportparams.caseset_filter(ctx.locals.caseset.case_ids,
+                                        ctx.locals.caseset.name)
+            ctx.push_page('report_edit', reportparams)
diff --git a/pages/casetask.html b/pages/casetask.html
new file mode 100644
index 0000000..7fa95ea
--- /dev/null
+++ b/pages/casetask.html
@@ -0,0 +1,34 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+<al-include name="taskbanner.html" />
+<al-include name="taskedit.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Case Task</al-setarg>
+ <al-expand name="taskbanner" />
+ <al-exec expr="field_rows = task_case.rows_and_cols('form')" />
+ <al-if expr="task_case.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <al-expand name="taskedit" />
+</al-expand>
diff --git a/pages/casetask.py b/pages/casetask.py
new file mode 100644
index 0000000..11cbfbb
--- /dev/null
+++ b/pages/casetask.py
@@ -0,0 +1,44 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, tasks
+from pages import page_common, taskedit
+import config
+
+pageops = taskedit.PageOps()
+
+def page_enter(ctx, task_case, inplace=False):
+    case_id = task_case.case_row.case_id
+    edittask = tasks.EditTask(globals.db, ctx.locals._credentials, 
+                              ctx.locals.task, case_id=case_id, inplace=inplace)
+    taskedit.page_enter(ctx, task_case.case_row.syndrome_id, edittask)
+    ctx.locals.task_case = task_case
+    ctx.add_session_vars('task_case')
+
+def page_leave(ctx):
+    taskedit.page_leave(ctx)
+    ctx.del_session_vars('task_case')
+
+def page_display(ctx):
+    taskedit.page_display(ctx)
+    ctx.locals.fh = taskedit.FormsHelper(ctx.locals.task_case, 
+                                         ctx.locals.edittask)
+    ctx.run_template('casetask.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/casetasks.py b/pages/casetasks.py
new file mode 100644
index 0000000..0e99b28
--- /dev/null
+++ b/pages/casetasks.py
@@ -0,0 +1,68 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, tasksearch, paged_search, tasks
+from pages import page_common, taskaction
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_edit(self, ctx, task_id):
+        ctx.locals.paged_search.reset()
+        try:
+            taskaction.task_dispatch(ctx, int(task_id), ctx.push_page, True)
+        except taskaction.TAError, e:
+            ctx.add_error(e)
+
+    def do_go(self, ctx, task_id):
+        ctx.locals.paged_search.reset()
+        try:
+            taskaction.task_dispatch(ctx, int(task_id), ctx.set_page)
+        except taskaction.TAError, e:
+            ctx.add_error(e)
+
+    def do_note(self, ctx, ignore):
+        if 'TASKINIT' in ctx.locals._credentials.rights:
+            ctx.push_page('notetask')
+
+    def do_results_prev_page(self, ctx, ignore):
+        ctx.locals.paged_search.prev()
+
+    def do_results_next_page(self, ctx, ignore):
+        ctx.locals.paged_search.next()
+
+pageops = PageOps()
+
+
+def page_enter(ctx, task_case):
+    assert ctx.locals.task is None
+    paged_search.push_pager(ctx, tasksearch.TaskSearch(globals.db, 
+                                          ctx.locals._credentials,
+                                          ctx.locals._credentials.prefs,
+                                          case_id=task_case.case_row.case_id))
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+
+def page_display(ctx):
+    page_common.unlock_task(ctx)
+    ctx.run_template('tasks.html')
+
+def page_process(ctx):
+    ctx.locals.paged_search.new_search()
+    pageops.page_process(ctx)
diff --git a/pages/dataimp.html b/pages/dataimp.html
new file mode 100644
index 0000000..c1736ba
--- /dev/null
+++ b/pages/dataimp.html
@@ -0,0 +1,272 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="tabs.html">
+
+<al-macro name="dataimp_sel">
+ <table border="0" class="gridbox">
+  <al-exec expr="available = editor.available()" />
+  <tr>
+   <th colspan="3" align="left">
+    Load an existing ruleset:
+   </th>
+  </tr>
+  <al-if expr="available">
+   <al-for iter="a_i" expr="available">
+    <al-exec expr="id, name = a_i.value()" />
+    <tr>
+     <td width="16"></td>
+     <td class="line"><al-value expr="name" /></td>
+     <td class="line" align="right">
+      <al-input type="submit" nameexpr="'load:%s' % id" 
+                 value="Use" class="butt" /></td>
+    </tr>
+   </al-for>
+  <al-else>
+   <tr>
+    <td width="16"></td>
+    <td colspan="2">
+     <b>No import rules are defined for this 
+      <al-value expr="config.syndrome_label" />.</b>
+    </td>
+   </tr>
+  </al-if>
+  <tr>
+   <th colspan="3" align="left">
+    Or create a new ruleset:
+   </th>
+  </tr>
+  <tr>
+   <td colspan="2"></td>
+   <td align="right">
+    <al-input type="submit" name="new" value="New" class="butt" />
+   </td>
+  </tr>
+  <tr>
+   <th colspan="3" align="left">
+    Or import an XML ruleset definition:
+   </th>
+  </tr>
+  <tr>
+   <td width="16"></td>
+   <td colspan="2" align="right">
+    <al-input type="file" name="imprules_file" size="40" class="file">
+    <al-input type="submit" name="imprules" value="Import" class="butt">
+   </td>
+  </tr>
+ </table> 
+</al-macro>
+
+<al-macro name="dataimp_params">
+ <al-input type="submit" class="right bigbutt danger" name="delete"
+        value="Delete Ruleset" />
+ <table border="0">
+   <tr>
+    <td><label for="name">Rule set name:</label></td>
+    <td><al-input size="30" name="editor.importrules.name" id="name" /></td>
+   </tr>
+   <tr>
+    <td><label for="mode">Column mode:</label></td>
+    <td>
+     <al-input type="radio" name="editor.importrules.mode" id="mode"
+       value="named" /> Named  
+     <al-input type="radio" name="editor.importrules.mode" id="mode"
+       value="positional" /> Positional
+    </td>
+   </tr>
+   <tr>
+    <td><label for="encoding">Data source encoding:</label></td>
+    <td>
+     <al-select name="editor.encoding" style="min-width: 10em;" id="encoding"
+      optionexpr="editor.encodings" />
+    </td>
+   </tr>
+   <tr>
+    <td><label for="fieldsep">Field separator:</label></td>
+    <td>
+     <al-select name="editor.fieldsep" style="min-width: 10em;"
+      optionexpr="editor.fieldseps" id="fieldsep" />
+    </td>
+   </tr>
+   <tr>
+    <td><label for="srclabel">Data source name:</label></td>
+    <td>
+     <al-input name="editor.importrules.srclabel"
+      style="min-width: 10em;" id="srclabel" />
+    </td>
+   </tr>
+   <tr>
+    <td><label for="conflicts">Conflicting records:</label></td>
+    <td>
+     <al-input type="radio" name="editor.importrules.conflicts" id="conflicts" 
+      value="ignore" /> Ignore  
+     <al-input type="radio" name="editor.importrules.conflicts" id="conflicts" 
+      value="duplicate" /> Duplicate  
+    </td>
+   </tr>
+ </table>
+</al-macro>
+
+<al-macro name="dataimp_fields">
+ <table border="0" class="gridbox">
+  <al-exec expr="view = editor.view(dataimp_src)" />
+  <al-if expr="view.unused_cols">
+   <tr><td colspan="3" class="rule"></td></tr>
+   <tr>
+    <th colspan="3" align="left">
+     Unused source columns:
+    </th>
+   </tr>
+   <tr>
+    <td colspan="3" align="left" class="srcsel">
+     <table>
+      <al-for iter="row_i" expr="view.unused_cols" cols="6">
+       <tr>
+        <al-for vars="col" expr="row_i.value()">
+         <td>
+          <label>
+           <al-input name="srcsel" valueexpr="col" type="radio" whitespace />
+           <al-value expr="col" />
+          </label>
+         </td>
+        </al-for>
+       </tr>
+      </al-for>
+     </table>
+    </td>
+   </tr>
+  </al-if>
+  <al-for vars="group" iter="group_i" expr="view">
+   <tr><td colspan="3" class="rule"></td></tr>
+   <tr>
+    <th colspan="3" align="left">
+     <al-if expr="group.name">
+      <al-input nameexpr="'del_form:%s' % group.name" class="smallbutt right"
+           type="submit" value="Del" />
+     </al-if>
+     <al-value expr="group.label" />
+    </th>
+   </tr>
+   <al-if expr="group">
+    <tr>
+     <th align="left">Target</th>
+     <th align="left">Action</th>
+     <th></th>
+    </tr>
+    <al-for vars="field" iter="field_i" expr="group">
+     <tr>
+      <td class="line"><al-value expr="field.label" /></td>
+      <td class="line"><al-value expr="field.action_desc" /></td>
+      <td class="line"><al-input type="submit" class="smallbutt"
+       nameexpr="'edit:%s:%s' % (group.name, field.name)" value="Edit" /></td>
+     </tr>
+    </al-for>
+   </al-if>
+   <tr>
+    <th colspan="3" align="right">
+     Add target: <al-select name="add_field" list
+      optionexpr="group.add_options" onchange="submit();" />
+     <al-if expr="not has_js">
+      <al-input type="submit" name="add_field_but" value="Add" />
+     </al-if>
+    </th>
+   </tr>
+  </al-for>
+  <al-if expr="view.add_options">
+   <tr><td colspan="3" class="rule"></td></tr>
+   <tr>
+    <th colspan="3" align="left">
+     Add form: <al-select name="add_form" 
+      optionexpr="view.add_options" onchange="submit();" />
+     <al-if expr="not has_js">
+      <al-input type="submit" name="add_form_but" value="Add" />
+     </al-if>
+    </th>
+   </tr>
+  </al-if>
+ </table>
+</al-macro>
+
+<al-macro name="dataimp_filters">
+</al-macro>
+
+<al-macro name="dataimp_upload">
+ <table valign="baseline">
+  <tr>
+    <th align="left" colspan="2">Upload a new file:</th>
+  </tr>
+  <tr>
+   <th> </th>
+   <td><al-input class="file" type="file" name="src_file" size="40" />
+       <al-input type="submit" class="butt" name="upload" value="Upload" /></td>
+  </tr>
+  <al-if expr="not dataimp_src">
+   <tr>
+    <th align="left" colspan="2">No file is currently selected.</th>
+   </tr>
+  <al-else>
+   <tr>
+    <th align="left" nowrap>Current file:</th>
+    <td>
+     <al-value expr="dataimp_src.name" /> (<al-value expr="dataimp_src.size" /> bytes)
+    <td>
+   </tr>
+   <tr>
+    <th align="left" nowrap></th>
+    <td>
+     <al-if expr="dataimp_src.preview">
+      <al-value expr="dataimp_src.preview.n_cols" /> columns,
+      <al-value expr="dataimp_src.preview.n_rows" /> rows
+     </al-if>
+    <td>
+   </tr>
+   <tr>
+    <th align="left" nowrap>Uploaded:</th>
+    <td><al-value expr="dataimp_src.received" /></td>
+   </tr>
+  </al-if>
+ </table>
+</al-macro>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Import <al-value expr="editor.syndrome().name" /> data</al-setarg>
+ <al-exec expr="tabs = dataimp_tabs" />
+ <al-expand name="left-tabs">
+  <al-expand name="confirm_or_error">
+   <al-lookup expr="dataimp_tabs.selected">
+    <al-item expr="'select'">
+     <al-expand name="dataimp_sel" />
+    </al-item>
+    <al-item expr="'params'">
+     <al-expand name="dataimp_params" />
+    </al-item>
+    <al-item expr="'fields'">
+     <al-expand name="dataimp_fields" />
+    </al-item>
+    <al-item expr="'filters'">
+     <al-expand name="dataimp_filters" />
+    </al-item>
+    <al-item expr="'upload'">
+     <al-expand name="dataimp_upload" />
+    </al-item>
+   </al-lookup>
+  </al-expand>
+ </al-expand>
+</al-expand>
diff --git a/pages/dataimp.py b/pages/dataimp.py
new file mode 100644
index 0000000..90535e5
--- /dev/null
+++ b/pages/dataimp.py
@@ -0,0 +1,201 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals
+from casemgr.dataimp import editor
+from casemgr.dataimp.datasrc import DataImpSrc, NullDataImpSrc
+from casemgr.tabs import Tabs
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def page_process(self, ctx):
+        ctx.locals.editor.update_rules()
+        src_file = getattr(ctx.locals, 'src_file', None)
+        if src_file:
+            if len(src_file) > 1:
+                raise page_common.PageError('Choose one file')
+            new_dataimp_src = DataImpSrc(src_file[0].filename, 
+                                         src_file[0].file)
+            if new_dataimp_src:
+                if ctx.locals.dataimp_src:
+                    ctx.locals.dataimp_src.release()
+                ctx.locals.dataimp_src = new_dataimp_src
+                self.update_preview(ctx)
+                if (ctx.locals.dataimp_src.preview 
+                        and ctx.locals.dataimp_src.preview.n_rows > 1000):
+                    ctx.msg('warn', 'WARNING: importing a large number of records'
+                                  ' may be very slow and time-outs may occur')
+
+        page_common.PageOpsBase.page_process(self, ctx)
+
+    def update_preview(self, ctx):
+        if ctx.locals.dataimp_src:
+            ctx.locals.dataimp_src.update_preview(ctx.locals.editor.importrules)
+            if ctx.locals.dataimp_src.preview.error:
+                ctx.add_error(ctx.locals.dataimp_src.preview.error)
+            return not bool(ctx.locals.dataimp_src.preview.error)
+
+    def unsaved_check(self, ctx):
+        if ctx.locals.editor.has_changed():
+            raise page_common.ConfirmSave
+
+    def rollback(self, ctx):
+        ctx.locals.editor.revert()
+
+    def commit(self, ctx):
+        ctx.locals.editor.save()
+        globals.db.commit()
+        ctx.add_message('Rule set %r saved' % 
+                        ctx.locals.editor.importrules.name) 
+
+    # Tab actions
+    def do_tab(self, ctx, tab):
+        if ctx.locals.dataimp_tabs.selected == 'params':
+            self.update_preview(ctx)
+        ctx.locals.dataimp_tabs.select(tab)
+
+    def do_view(self, ctx, ignore):
+        if ctx.locals.dataimp_src:
+            if self.update_preview(ctx):
+                ctx.push_page('dataimp_view')
+        else:
+            ctx.add_error('No data to view')
+
+    def do_save(self, ctx, ignore):
+        if not ctx.locals.editor.importrules.name:
+            ctx.locals.dataimp_tabs.select('params')
+            ctx.add_error('Specify a rule set name before saving') 
+        else:
+            self.commit(ctx)
+
+    def do_revert(self, ctx, ignore):
+        if ctx.locals.editor.has_changed():
+            if not self.confirmed:
+                raise page_common.ConfirmRevert
+            ctx.locals.editor.revert()
+
+    def do_export(self, ctx, ignore):
+        downloader = page_common.download(ctx, 'importdef.xml')
+        downloader.write(ctx.locals.editor.rules_xml())
+
+    def do_import(self, ctx, ignore):
+        if ctx.locals.dataimp_src:
+            if self.update_preview(ctx):
+                ctx.push_page('dataimp_preview')
+        else:
+            ctx.add_error('No data uploaded')
+
+    # Rule sets tab
+    def do_new(self, ctx, ignore):
+        if ctx.locals.editor.has_changed() and not self.confirmed:
+            raise page_common.ConfirmSave
+        ctx.locals.editor = editor.new(ctx.locals.editor.syndrome_id)
+        ctx.locals.dataimp_tabs.select('params')
+
+    def do_load(self, ctx, id):
+        if ctx.locals.editor.has_changed() and not self.confirmed:
+            raise page_common.ConfirmSave
+        syndrome_id = ctx.locals.editor.syndrome_id
+        ctx.locals.editor = editor.load(ctx, syndrome_id, int(id))
+        ctx.locals.dataimp_tabs.select('params')
+
+    def do_imprules(self, ctx, ignore):
+        if ctx.locals.confirm is not None:
+            new_editor = ctx.locals.confirm.editor
+        else:
+            if len(ctx.locals.imprules_file) != 1:
+                raise page_common.PageError('Choose one file')
+            file = ctx.locals.imprules_file[0].file
+            syndrome_id = ctx.locals.editor.syndrome_id
+            new_editor = editor.load_file(ctx, syndrome_id, None, file)
+            if ctx.locals.editor.has_changed():
+                raise page_common.ConfirmSave(editor=new_editor)
+        ctx.locals.editor = new_editor
+        ctx.locals.dataimp_tabs.select('params')
+
+    # Params tab
+    def do_delete(self, ctx, ignore):
+        if ctx.locals.editor.def_id is None:
+            if not self.confirmed and ctx.locals.editor.has_changed():
+                raise page_common.ConfirmDelete
+            ctx.locals.editor.revert()
+        else:
+            if not self.confirmed:
+                raise page_common.ConfirmDelete
+            ctx.locals.editor.delete()
+            globals.db.commit()
+        ctx.locals.dataimp_tabs.select('select')
+
+    # Fields tab
+    def do_edit(self, ctx, group, field):
+        ctx.push_page('dataimp_editfield', group, field)
+
+    def do_add_field(self, ctx, field):
+        ctx.locals.add_field = None
+        group, field = ctx.locals.editor.add_field(field, ctx.locals.srcsel)
+        ctx.push_page('dataimp_editfield', group, field)
+        ctx.locals.srcsel = None
+
+    def do_add_form(self, ctx, form):
+        ctx.locals.add_form = None
+        ctx.locals.editor.add_form(form)
+
+    def do_del_form(self, ctx, form):
+        ctx.locals.editor.del_form(form)
+
+page_process = PageOps().page_process
+
+
+def make_tabs():
+    tabs = Tabs()
+    tabs.add('select', 'Rule sets')
+    tabs.spacer()
+    tabs.add('params', 'Params')
+    tabs.spacer()
+    tabs.add('upload', 'Upload Data')
+    tabs.add('view', 'View Data', action=True)
+    tabs.spacer()
+    tabs.add('fields', 'Fields')
+    tabs.add('save', 'Save Rules', action=True)
+    tabs.add('export', 'XML Export', action=True)
+    tabs.add('revert', 'Revert Rules', action=True, danger=True)
+    tabs.spacer()
+    tabs.add('import', 'Import', action=True)
+    tabs.done()
+    return tabs
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.dataimp_tabs = make_tabs()
+    ctx.locals.editor = editor.new(syndrome_id)
+    ctx.locals.dataimp_src = NullDataImpSrc
+    ctx.locals.srcsel = None
+    ctx.add_session_vars('dataimp_tabs', 'editor', 'dataimp_src', 'srcsel')
+
+def page_leave(ctx):
+    dataimp_src = getattr(ctx.locals, 'dataimp_src', None)
+    if dataimp_src:
+        dataimp_src.release()
+    ctx.del_session_vars('dataimp_tabs', 'editor', 'dataimp_src', 'srcsel')
+
+def page_display(ctx):
+    if not page_common.send_download(ctx):
+        ctx.run_template('dataimp.html')
diff --git a/pages/dataimp_editfield.html b/pages/dataimp_editfield.html
new file mode 100644
index 0000000..17909c2
--- /dev/null
+++ b/pages/dataimp_editfield.html
@@ -0,0 +1,274 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="dataimp_translations">
+   <al-if expr="editfield.is_datefield">
+     <tr>
+      <td valign="top"><label>Date format:</label></td>
+      <td>
+       <al-for iter="f_i" expr="editfield.date_formats">
+        <al-input name="editfield.date_format" type="radio" onclick="submit();"
+          valueexpr="f_i.value()"> <al-value expr="f_i.value()" /><br>
+       </al-for>
+       <label>Other:</label><al-input name="editfield.date_format_other">
+      </td>
+     </tr>
+   </al-if>
+
+   <al-if expr="editfield.is_filterable">
+    <al-exec expr="colvals = editfield.colvalues(dataimp_src)">
+    <al-if expr="not editfield.ignore_case">
+     <tr>
+      <td valign="top"><label>Case:</label></td>
+      <td>
+       <al-for iter="c_i" expr="editfield.case_options">
+        <al-input type="radio" name="editfield.case" onclick="submit();"
+          valueexpr="c_i.value()[0]">
+         <al-value expr="c_i.value()[1]" />  
+       </al-for>
+      </td>
+     </tr>
+    </al-if>
+    <tr>
+     <td valign="top"><label>Filters:</label></td>
+     <td>
+      <table>
+       <al-for iter="f_i" expr="editfield.translations">
+        <al-exec expr="_translation = f_i.value()" />
+        <tr>
+         <td><al-value expr="_translation.type"></td>
+         <td>
+          <al-if expr="_translation.type == 'Translate' and colvals">
+           <al-select optionexpr="colvals"
+            nameexpr="'editfield.translations[%d].match' % f_i.index()" />
+          <al-else>
+           <al-input nameexpr="'editfield.translations[%d].match' % f_i.index()">
+          </al-if>
+         </td>
+         <td>
+          <al-if expr="editfield.field_options">
+           <al-select nameexpr="'editfield.translations[%d].to' % f_i.index()"
+            optionexpr="[(CHOOSE, CHOOSE)] + editfield.field_options" />
+          <al-else>
+           <al-input nameexpr="'editfield.translations[%d].to' % f_i.index()">
+          </al-if>
+         </td>
+         <td>
+          Ignore case? 
+          <al-input type="checkbox" value="True"
+           nameexpr="'editfield.translations[%d].ignorecase' % f_i.index()">
+         </td>
+         <td>
+          <al-input type="submit" class="smallbutt" value="Del"
+           nameexpr="'del_translate:%d' % f_i.index()">
+         </td>
+        </tr>
+       </al-for>
+       <tr>
+        <td colspan="5" align="right">
+         <al-input type="submit" name="add_translate:normal" class="bigbutt"
+          value="New Translation" />
+         <al-input type="submit" name="add_translate:regexp" class="bigbutt"
+          value="New RegExp" />
+        </td>
+       </tr>
+       <al-if expr="editfield.field_options">
+        <al-exec expr="missing = editfield.get_field_options_sorted(colvals)" />
+        <al-if expr="missing">
+         <tr>
+          <td colspan="3">
+           Missing translations: 
+           <al-value expr="', '.join(missing)" />
+          </td>
+          <td colspan="2" align="right">
+           <al-input type="submit" name="add_field_opts" class="bigbutt"
+            value="Add as Translations" />
+          </td>
+         </tr>
+        </al-if>
+       </al-if>
+      </table>
+     </td>
+    </tr>
+   </al-if>
+</al-macro>
+
+<al-macro name="import_this_column">
+     <tr>
+      <td colspan="2" class="eif-label">
+       <al-if expr="editor.importrules.mode == 'named'">
+        Import this field from the named column (first data row names
+        each column)
+       <al-else>
+        Import this field from column number
+       </al-if><al-usearg />
+      </td>
+     </tr>
+</al-macro>
+
+<al-macro name="import_src_col">
+     <tr>
+      <td>
+       <label for="editfield.selected.src">Source field:</label>
+      </td>
+      <td>
+       <al-if expr="dataimp_src.preview">
+        <al-select name="editfield.selected.src" onchange="submit();"
+         optionexpr="editfield.src_fields(dataimp_src)" />
+        <al-if expr="not has_js">
+         <al-input type="submit" class="butt" name="apply" value="Apply" />
+        </al-if>
+       <al-else>
+        <al-input name="editfield.selected.src" />
+       </al-if>
+      </td>
+     </tr>
+</al-macro>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Import rule for <al-value expr="repr(editfield.label)" /> field</al-setarg>
+ <div class="ttabs">
+  <al-for iter="b_i" expr="editfield.actions">
+   <al-exec expr="action = b_i.value()" />
+   <al-if expr="action.selected">
+    <al-input type="submit"  class="curr" disabled
+     nameexpr="'setaction:%s' % action.action_name" expr="action.desc" />
+   <al-elif expr="action.initial">
+    <al-input type="submit" class="initial"
+     nameexpr="'setaction:%s' % action.action_name" expr="action.desc" />
+   <al-else>
+    <al-input type="submit" 
+     nameexpr="'setaction:%s' % action.action_name" expr="action.desc" />
+   </al-if>
+  </al-for>
+ </div>
+
+ <al-if expr="dataimp_src and getattr(editfield.selected, 'src', None)">
+  <table border="2" class="imp-preview">
+   <tr><th><al-value expr="editfield.selected.src"></th></tr>
+   <al-for iter="c_i" expr="editfield.preview(dataimp_src)">
+    <tr><td><al-value expr="c_i.value()" /></td></tr>
+   </al-for>
+   <tr><td>...</td></tr>
+  </table>
+ </al-if>
+
+ <table class="edit-imp-field">
+   <al-lookup expr="editfield.selected.action_name">
+
+    <al-item expr="'source'">
+     <al-expand name="import_this_column" />
+     <al-expand name="import_src_col" />
+     <al-expand name="dataimp_translations" />
+    </al-item>
+
+    <al-item expr="'agesource'">
+     <al-expand name="import_this_column" />
+     <tr>
+      <td>
+       <label for="editfield.selected.age">
+        <al-if expr="editor.importrules.mode == 'named'">
+         Age field name
+        <al-else>
+         Age field index
+        </al-if>
+       </label>
+      </td>
+      <td>
+       <al-if expr="dataimp_src.preview">
+        <al-select name="editfield.selected.age" onchange="submit();"
+         optionexpr="[CHOOSE] + dataimp_src.preview.col_names" />
+       <al-else>
+        <al-input name="editfield.selected.age" />
+       </al-if>
+      </td>
+     </tr>
+     <tr>
+      <td>
+       <label for="editfield.selected.src">DOB source field:</label>
+      </td>
+      <td>
+       <al-if expr="dataimp_src.preview">
+        <al-select name="editfield.selected.src" onchange="submit();"
+         optionexpr="[CHOOSE] + dataimp_src.preview.col_names" />
+        <al-if expr="not has_js">
+         <al-input type="submit" class="butt" name="apply" value="Apply" />
+        </al-if>
+       <al-else>
+        <al-input name="editfield.selected.src" />
+       </al-if>
+      </td>
+     </tr>
+     <al-expand name="dataimp_translations" />
+    </al-item>
+
+    <al-item expr="'multivalue'">
+     <al-expand name="import_this_column">, spliting on the supplied
+       delimiter</al-expand>
+     <al-expand name="import_src_col" />
+     <tr>
+      <td>
+       <label for="editfield.selected.delimiter">Delimiter:</label>
+      </td>
+      <td>
+       <al-select name="editfield.selected.delimiter" 
+           optionexpr="editfield.selected.delimiter_options" 
+           onchange="submit();" />
+      </td>
+     </tr>
+     <al-expand name="dataimp_translations" />
+    </al-item>
+
+    <al-item expr="'fixed'">
+     <tr>
+      <td colspan="2" class="eif-label">
+       On imported records, set this field to the value given.
+      </td>
+     </tr>
+     <tr>
+      <td><label for="editfield.selected.value">Field value</label></td>
+      <td>
+       <al-if expr="editfield.field_options">
+        <al-select name="editfield.selected.value" 
+         optionexpr="[CHOOSE] + editfield.field_options" />
+       <al-else>
+        <al-input name="editfield.selected.value" />
+       </al-if>
+      </td>
+     </tr>
+    </al-item>
+
+    <al-item expr="'ignore'">
+     <tr>
+      <td colspan="2" class="eif-label">
+       Ignore this field while importing.
+      </td>
+     </tr>
+    </al-item>
+   </al-lookup>
+
+  <tr>
+   <td colspan="2" align="right">
+    <al-input type="submit" class="butt" name="okay" value="Okay" />
+   </td>
+  </tr>
+
+ </table> 
+</al-expand>
diff --git a/pages/dataimp_editfield.py b/pages/dataimp_editfield.py
new file mode 100644
index 0000000..184f49b
--- /dev/null
+++ b/pages/dataimp_editfield.py
@@ -0,0 +1,63 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+from casemgr.dataimp import Error
+from casemgr.dataimp.editor import CHOOSE
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_setaction(self, ctx, action):
+        ctx.locals.editfield.set_action(action)
+
+    def do_add_translate(self, ctx, mode):
+        ctx.locals.editfield.add_translate(mode == 'regexp')
+
+    def do_del_translate(self, ctx, index):
+        ctx.locals.editfield.del_translate(int(index))
+
+    def do_add_field_opts(self, ctx, index):
+        colvals = ctx.locals.editfield.colvalues(ctx.locals.dataimp_src)
+        ctx.locals.editfield.add_field_opts(colvals)
+
+    def do_okay(self, ctx, ignore):
+        ctx.locals.editor.save_edit_field(ctx.locals.editfield)
+        ctx.pop_page()
+
+    def do_back(self, ctx, ignore):
+        ctx.pop_page()
+
+def page_process(ctx):
+    try:
+        ctx.locals.editfield.trial_translate(ctx.locals.dataimp_src)
+    except Error, e:
+        ctx.add_error(e)
+    PageOps().page_process(ctx)
+
+def page_enter(ctx, group_name, field_name):
+    ctx.locals.editfield = ctx.locals.editor.edit_field(group_name, field_name)
+    ctx.add_session_vars('editfield')
+
+def page_leave(ctx):
+    ctx.del_session_vars('editfield')
+
+def page_display(ctx):
+    ctx.run_template('dataimp_editfield.html')
+
diff --git a/pages/dataimp_preview.html b/pages/dataimp_preview.html
new file mode 100644
index 0000000..e7677ac
--- /dev/null
+++ b/pages/dataimp_preview.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner_scroll">
+ <al-setarg name="title">Import <al-value expr="editor.syndrome().name" /> data - preview</al-setarg>
+ <al-if expr="not preview.errors">
+  <al-input type="submit" name="excluded" value="Conflicting Records" 
+    class="right bigbutt">
+  <al-input type="submit" name="import" value="Import" class="danger bigbutt">
+ </al-if>
+  <table class="gridtab">
+   <tr>
+    <th rowspan="2">row</th>
+    <al-for vars="group, span" expr="preview.group_header">
+     <al-th colspanexpr="span" align="center">
+      <al-value expr="group" /></al-th>
+    </al-for>
+   </tr>
+   <tr>
+    <al-for vars="label" expr="preview.header">
+     <th align="left"><al-value expr="label" /></th>
+    </al-for>
+   </tr>
+   <al-for vars="row" iter="r_i" expr="preview.rows">
+    <al-exec expr="rownum = r_i.index() + 1" />
+    <al-exec expr="errors = preview.errors.get(rownum)" />
+    <tr>
+     <al-td rowspanexpr="len(errors) + 1"><al-value expr="rownum" /></al-td>
+     <al-for iter="c_i" expr="row">
+      <td>
+       <al-if expr="c_i.value()">
+        <al-value expr="c_i.value()" />
+       </al-if>
+      </td>
+     </al-for>
+    </tr>
+    <al-for vars="msg" expr="errors">
+     <tr>
+      <al-td colspanexpr="preview.n_cols" class="err">
+       <al-value expr="msg" />
+      </al-td>
+     </tr>
+    </al-for>
+   </al-for>
+  </table>
+</al-expand>
diff --git a/pages/dataimp_preview.py b/pages/dataimp_preview.py
new file mode 100644
index 0000000..d021113
--- /dev/null
+++ b/pages/dataimp_preview.py
@@ -0,0 +1,75 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, persondupe
+from casemgr.dataimp.dataimp import DataImp, PreviewImport, locked_case_ids
+
+from pages import page_common, caseset_ops
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_import(self, ctx, ignore):
+        imp = DataImp(ctx.locals._credentials,
+                      ctx.locals.editor.syndrome_id, 
+                      ctx.locals.dataimp_src, 
+                      ctx.locals.editor.importrules)
+        if imp.errors:
+            for error in imp.errors.get(None):
+                ctx.msg('err', error)
+        else:
+            globals.db.commit()
+            ctx.add_message(imp.status)
+            ctx.locals.dataimp_src.release()
+            ctx.locals.dataimp_tabs.select('select')
+            if imp.locked_cases:
+                ctx.add_message('record ID(s) %s were source-locked and have '
+                                'not been updated.' % 
+                                ', '.join(map(str, imp.locked_cases)))
+#                caseset_ops.make_caseset(ctx, imp.locked_cases,
+#                            'Data import source-locked cases')
+            if imp.conflict_cnt:
+                dp = persondupe.loadconflicts(globals.db)
+                ctx.push_page('dupepersons', dp, 'Import conflicts')
+            else:
+                ctx.pop_page()
+
+    def do_excluded(self, ctx, ignore):
+        case_ids = locked_case_ids(ctx.locals._credentials,
+                                   ctx.locals.editor.syndrome_id, 
+                                   ctx.locals.dataimp_src, 
+                                   ctx.locals.editor.importrules)
+        caseset_ops.make_caseset(ctx, case_ids,
+                            'Data import source-locked cases')
+
+
+
+page_process = PageOps().page_process
+
+def page_display(ctx):
+    ctx.locals.preview = PreviewImport(ctx.locals._credentials,
+                                       ctx.locals.editor.syndrome_id, 
+                                       ctx.locals.dataimp_src, 
+                                       ctx.locals.editor.importrules)
+    if ctx.locals.preview.errors:
+        for error in ctx.locals.preview.errors.get():
+            ctx.msg('err', error)
+        ctx.msg('err', '%s error(s) occurred - fix before importing' %\
+                                        ctx.locals.preview.errors.count())
+    ctx.run_template('dataimp_preview.html')
diff --git a/pages/dataimp_view.html b/pages/dataimp_view.html
new file mode 100644
index 0000000..9004be9
--- /dev/null
+++ b/pages/dataimp_view.html
@@ -0,0 +1,39 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner_scroll">
+ <al-setarg name="title">Import <al-value expr="editor.syndrome().name" /> data - view data</al-setarg>
+ <table class="gridtab">
+  <al-if expr="dataimp_src.preview.col_names">
+   <tr>
+    <al-for iter="c_i" expr="dataimp_src.preview.col_names">
+     <th align="left"><al-value expr="c_i.value()" /></th>
+    </al-for>
+   </tr>
+  </al-if>
+  <al-for iter="r_i" expr="dataimp_src.preview.rows">
+   <tr>
+    <al-for iter="c_i" expr="r_i.value()">
+     <td><al-value expr="c_i.value()" /></td>
+    </al-for>
+   </tr>
+  </al-for>
+ </table>
+</al-expand>
diff --git a/pages/dataimp_view.py b/pages/dataimp_view.py
new file mode 100644
index 0000000..eebb5f4
--- /dev/null
+++ b/pages/dataimp_view.py
@@ -0,0 +1,26 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+
+import config
+
+page_process = page_common.page_process
+
+def page_display(ctx):
+    ctx.run_template('dataimp_view.html')
diff --git a/pages/demogfields.html b/pages/demogfields.html
new file mode 100644
index 0000000..12cb0a0
--- /dev/null
+++ b/pages/demogfields.html
@@ -0,0 +1,161 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-lookup name="field_render">
+ <al-item expr="'value'">
+  <al-div idexpr="field.field">
+   <al-value expr="eval(field.field)" />
+  </al-div>
+ </al-item>
+
+ <al-item expr="'textinput'">
+  <al-input nameexpr="field.field" idexpr="field.field" 
+   disabledbool="field.disabled" />
+ </al-item>
+
+ <al-item expr="'textarea'">
+  <al-textarea rows="4" nameexpr="field.field" idexpr="field.field" 
+   disabledbool="field.disabled" />
+ </al-item>
+
+ <al-item expr="'checkbox'">
+  <al-input type="checkbox" class="autowidth" value="True"
+   nameexpr="field.field" idexpr="field.field" 
+   disabledbool="field.disabled" />
+ </al-item>
+
+ <al-item expr="'short_radio'">
+  <al-for iter="i" expr="field.optionexpr()">
+   <al-exec expr="value, label = i.value()" />
+   <al-input type="radio" class="autowidth"
+    nameexpr="field.field" idexpr="field.field"
+    valueexpr="value"> <al-value expr="label" />  
+  </al-for>
+ </al-item>
+
+ <al-item expr="'datetimeinput'">
+  <al-input nameexpr="field.field" idexpr="field.field" 
+   calendarformatexpr="field.format()"
+   disabledbool="field.disabled" />
+ </al-item>
+
+ <al-item expr="'select'">
+  <al-select nameexpr="field.field" idexpr="field.field"
+    optionexpr="field.optionexpr()" disabledbool="field.disabled" />
+ </al-item>
+
+ <al-item expr="'select_syndrome'">
+  <al-if expr="syndromes">
+   <al-select nameexpr="field.field" idexpr="field.field"
+     optionexpr="syndromes.anyoptions()" 
+     disabledbool="field.disabled or search.syndrome_id" />
+  </al-if>
+ </al-item>
+
+ <al-item expr="'case_dob'">
+  <al-exec expr="agestr = field.age_if_dob(__ctx__.locals)" />
+  <al-if expr="not agestr">
+   <al-input 
+    calendarformatexpr="field.format()"
+    nameexpr="field.field"
+    idexpr="field.field" disabledbool="field.disabled" />
+  <al-else>
+   <div class="short">
+    <al-input 
+     calendarformatexpr="field.format()"
+     nameexpr="field.field"
+     idexpr="field.field" disabledbool="field.disabled" />
+   </div>Age: <al-value expr="agestr" />
+  </al-if>
+ </al-item>
+
+ <al-item expr="'tags'">
+  <div class="fieldandbutt">
+   <div class="andbutt">
+    <al-input type="submit" name="tagbrowse" value="Browse" />
+   </div>
+   <div class="fieldand">
+    <al-input class="thebutt" nameexpr="field.field" idexpr="field.field" 
+     disabledbool="field.disabled" />
+   </div>
+  </div>
+ </al-item>
+
+</al-lookup>
+
+<al-macro name="demogfields">
+  <table class="labelform" width="99%">
+   <al-for vars="group" expr="render_fields.grouped()">
+    <al-if expr="group.label">
+     <al-tbody class="grouplabel" idexpr="'label_%s' % group.name">
+      <tr>
+       <th colspan="4">
+        <al-span class="foldicon" idexpr="'icon_%s' % group.name" />
+        <al-value expr="group.label" />
+       </th>
+      </tr>
+     </al-tbody>
+    </al-if>
+    <al-tbody class="details" idexpr="'fold_%s' % group.name">
+     <al-for iter="field_row_i" expr="group.rows_and_cols()">
+      <tr>
+       <al-for iter="field_i" expr="field_row_i.value()">
+        <al-exec expr="field = field_i.value()" />
+        <td class="label">
+         <al-label forexpr="field.field">
+          <al-value expr="field.label" />
+         </al-label>
+        </td>
+        <td class="field">
+         <al-value lookup="field_render" expr="field.render" />
+        </td>
+       </al-for>
+      </tr>
+     </al-for>
+    </al-tbody>
+   </al-for>
+  </table>
+  <al-for vars="group" expr="render_fields.grouped()">
+   <al-if expr="group.name">
+    <script type="text/javascript"><al-value noescape expr="'linkfold(%r,true);' % group.name" /></script>
+   </al-if>
+  </al-for>
+</al-macro>
+
+<al-macro name="demogfields_text">
+  <table class="labeltext">
+   <al-for iter="field_row_i" expr="field_rows">
+    <tr>
+     <al-for iter="field_i" expr="field_row_i.value()">
+      <al-exec expr="field = field_i.value()" />
+      <td class="label">
+        <al-value expr="field.label" />
+      </td>
+      <td class="field">
+       <al-exec expr="value = field.outtrans(__ctx__.locals)" />
+       <al-if expr="value">
+        <al-value expr="field.outtrans(__ctx__.locals)" />
+       </al-if>
+      </td>
+     </al-for>
+    </tr>
+   </al-for>
+  </table>
+</al-macro>
diff --git a/pages/dupecases.html b/pages/dupecases.html
new file mode 100644
index 0000000..f534c3b
--- /dev/null
+++ b/pages/dupecases.html
@@ -0,0 +1,62 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Duplicate Cases for <al-value expr="case_dups.syndrome_name" /></al-setarg>
+ <table class="duperes">
+  <thead>
+   <tr>
+    <al-th colspanexpr="len(case_dups.fields) + 2">
+     <al-expand name="page_select" />
+    </al-th>
+   </tr>
+  </thead>
+  <tbody>
+   <al-for vars="person" expr="case_dups.result_page()">
+    <tr>
+     <al-th colspanexpr="len(case_dups.fields) + 2">
+      <al-value expr="person.summary()" />
+     </al-th>
+    </tr>
+    <tr class="subhead">
+     <td class="indent"> </td>
+     <al-for vars="field" expr="case_dups.fields">
+      <th><al-value expr="field.label" /></th>
+     </al-for>
+     <th> </th>
+    </tr>
+    <al-for vars="cs" iter="cs_i" expr="person.cases">
+     <tr>
+      <td class="indent"> </td>
+      <al-for vars="field" expr="case_dups.fields">
+       <td><al-value expr="field.outtrans(cs.row)" /></td>
+      </al-for>
+      <al-if expr="cs_i.index() == 0">
+       <al-td rowspanexpr="len(person.cases)" align="right">
+        <al-input type="submit" class="butt" value="Select"
+          nameexpr="'merge:%s' % person.person_id" />
+       </al-td>
+      </al-if>
+     </tr>
+    </al-for>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/dupecases.py b/pages/dupecases.py
new file mode 100644
index 0000000..710d544
--- /dev/null
+++ b/pages/dupecases.py
@@ -0,0 +1,50 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, casedupe, paged_search, casemerge
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    
+    def do_merge(self, ctx, person_id):
+        selmerge = casemerge.by_person_id(ctx.locals._credentials, 
+                                          ctx.locals.case_dups.syndrome_id,
+                                          int(person_id))
+        ctx.push_page('selmergecase', selmerge)
+
+
+pageops = PageOps()
+
+
+def page_enter(ctx, syndrome_id, notification_window):
+    ctx.locals.case_dups = casedupe.CaseDupeScan(ctx.locals._credentials.prefs,
+                                                 syndrome_id, 
+                                                 notification_window)
+    paged_search.push_pager(ctx, ctx.locals.case_dups)
+    ctx.add_session_vars('case_dups')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('case_dups')
+
+def page_display(ctx):
+    ctx.run_template('dupecases.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/dupecases_config.html b/pages/dupecases_config.html
new file mode 100644
index 0000000..bec1bd2
--- /dev/null
+++ b/pages/dupecases_config.html
@@ -0,0 +1,51 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Duplicate Cases</al-setarg>
+ <table class="widelabelform">
+  <tr>
+   <td class="label">
+    <label for="syndrome_id"><al-value expr="config.syndrome_label" /></label>
+   </td>
+   <td class="field">
+    <al-select name="syndrome_id" optionexpr="syndromes.options()" />
+   </td>
+  </tr>
+  <al-comment>
+  <tr>
+   <td class="label">
+    <label for="notification_window">
+     Notification window (in days)
+    </label>
+   </td>
+   <td class="field">
+    <al-input name="notification_window" />
+   </td>
+  </tr>
+  </al-comment>
+  <tr>
+   <th colspan="2" style="text-align: right;">
+    <al-input type="submit" class="butt" name="scan" value="Scan" />
+   </th>
+  </tr>
+ </table>
+</al-expand>
+
diff --git a/pages/dupecases_config.py b/pages/dupecases_config.py
new file mode 100644
index 0000000..41fe46e
--- /dev/null
+++ b/pages/dupecases_config.py
@@ -0,0 +1,45 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, casedupe
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_scan(self, ctx, ignore):
+        #window = ctx.locals.notification_window
+        #if window:
+        #    window = window.strip()
+        #if window:
+        #    window = int(window)
+        #else:
+        #    window = None
+        window = None
+        ctx.set_page('dupecases', int(ctx.locals.syndrome_id), window)
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    if not ctx.locals.syndromes:
+        raise page_common.PageError('No %s' % config.syndrome_label)
+
+def page_display(ctx):
+    ctx.run_template('dupecases_config.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/dupepersons.html b/pages/dupepersons.html
new file mode 100644
index 0000000..943c9bd
--- /dev/null
+++ b/pages/dupepersons.html
@@ -0,0 +1,96 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="likely.title" /></al-setarg>
+ <div class="smaller">Last update: <al-value expr="likely.last_run" /></div>
+ <table class="duperes" width="100%">
+  <thead>
+   <tr>
+    <al-th colspanexpr="len(fields) + 2">
+     <al-expand name="page_select" />
+    </al-th>
+   </tr>
+   <tr>
+    <th style="text-align: center;">Confidence</th>
+    <al-for iter="f_i" expr="fields">
+     <th>
+      <al-value expr="f_i.value().label" />
+     </th>
+    </al-for>
+    <th style="text-align: center;">
+     <al-if expr="likely.exclude_count">
+      <al-if expr="likely.show_excluded">
+       <al-input type="submit" class="butt" name="excluded:hide" value="Hide Excl" />
+      <al-else>
+       <al-input type="submit" class="butt" name="excluded:show" value="Show Excl" />
+      </al-if>
+     </al-if>
+    </th>
+   </tr>
+  </thead>
+  <tfoot>
+   <tr><al-td colspanexpr="len(fields) + 2" class="smaller">
+    <al-value expr="likely.stats()" />
+   </al-td></tr>
+   <tr>
+    <al-th colspanexpr="len(fields) + 2">
+     <al-expand name="page_select" />
+    </al-th>
+   </tr>
+  </tfoot>
+  <tbody>
+   <al-for iter="page_i" expr="page">
+    <al-exec expr="index, pair, person_a, person_b = page_i.value()" />
+    <al-if expr="page_i.index() & 1"><tr class="darker"><al-else><tr></al-if>
+     <al-if expr="pair.status == persondupe.STATUS_EXCLUDED">
+      <td rowspan="2" class="excluded" align="center">
+       Excluded<br>
+       <al-if expr="pair.exclude_reason"><al-value expr="pair.exclude_reason" /><br></al-if>
+       <al-value expr="pair.confpc()" />
+      </td>
+     <al-else>
+      <td rowspan="2" align="center">
+       <al-value expr="pair.confpc()" />
+      </td>
+     </al-if>
+     <al-for iter="f_i" expr="fields">
+      <al-exec expr="field = f_i.value()" />
+      <td>
+       <al-value expr="field.outtrans(person_a) or ''" />
+      </td>
+     </al-for>
+     <td rowspan="2" align="center">
+      <al-input type="submit" class="butt" 
+       nameexpr="'view:%d' % index" value="View" />
+     </td>
+    </tr>
+    <al-if expr="page_i.index() & 1"><tr class="darker"><al-else><tr></al-if>
+     <al-for iter="f_i" expr="fields">
+      <al-exec expr="field = f_i.value()" />
+      <td>
+       <al-value expr="field.outtrans(person_b) or ''" />
+      </td>
+     </al-for>
+    </tr>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/dupepersons.py b/pages/dupepersons.py
new file mode 100644
index 0000000..8dd4bbe
--- /dev/null
+++ b/pages/dupepersons.py
@@ -0,0 +1,137 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from time import time
+from casemgr import globals, demogfields, paged_search, persondupe, personmerge
+from pages import page_common
+import config
+
+
+class LikelyMatches(paged_search.Pager):
+
+    def __init__(self, likely, title):
+        self.title = title
+        self.show_excluded = False
+        self.current = None
+        self.full_likely = likely
+        self.last_run = persondupe.last_run(globals.db)
+        self.new_search()
+
+    def new_search(self):
+        self.reset_page()
+        self.filter_likely()
+
+    def filter_likely(self):
+        included = [m for m in self.full_likely 
+                    if m.status != persondupe.STATUS_EXCLUDED]
+        self.exclude_count = len(self.full_likely) - len(included)
+        if self.show_excluded:
+            self.likely = self.full_likely
+        else:
+            self.likely = included
+
+    def set_show_excluded(self, show_excluded):
+        self.show_excluded = show_excluded
+        self.page = 1
+
+    def result_count(self):
+        return len(self.likely)
+
+    def result_page(self):
+        st = time()
+        self.filter_likely()
+        page_length = self.page_length()
+        start = (self.page - 1) * page_length
+        end = min(start + page_length, len(self.likely))
+        persons = globals.db.table_dict('persons')
+        for pair in self.likely[start:end]:
+            persons.want(pair.low_person_id)
+            persons.want(pair.high_person_id)
+        persons.preload()
+        page = []
+        for i in range(start, end):
+            pair = self.likely[i]
+            try:
+                person_a = persons[pair.low_person_id]
+                person_b = persons[pair.high_person_id] 
+            except KeyError:
+                continue        # Possibly someone else merging.
+            page.append((i, pair, person_a, person_b))
+        self.page_time = time() - st
+        return page
+
+    def page_offs(self, index):
+        return self.likely[(self.page - 1) * self.page_length() + index]
+
+    def set_cur(self, index):
+        self.current = self.likely[index]
+        return self.current
+
+    def set_cur_exclude(self, status, exclude_reason):
+        if self.current is not None:
+            self.current.status = status
+            self.current.exclude_reason = exclude_reason
+
+    def stats(self):
+        return ''
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_view(self, ctx, index):
+        matchpair = ctx.locals.likely.set_cur(int(index))
+        merge = personmerge.Merge(matchpair.low_person_id, 
+                                  matchpair.high_person_id)
+        ctx.push_page('mergeperson', merge)
+
+    def do_excluded(self, ctx, op):
+        ctx.locals.likely.set_show_excluded(op == 'show')
+
+pageops = PageOps()
+
+
+def page_enter(ctx, dp, title):
+    ctx.locals.likely = LikelyMatches(dp, title)
+    paged_search.push_pager(ctx, ctx.locals.likely)
+    ctx.add_session_vars('likely')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('likely')
+
+show_fields = (
+    'surname', 'given_names', 'sex', 'DOB', 
+    'street_address', 'locality', 'state',
+)
+
+def page_display(ctx):
+    ctx.locals.page = ctx.locals.likely.result_page()
+    fields = demogfields.get_demog_fields(globals.db, None)
+    ctx.locals.fields = [f for f in fields.context_fields('result')
+                         if f.name in show_fields]
+    if not ctx.locals.likely.full_likely:
+        ctx.add_error('No duplicate matches found')
+    elif not ctx.locals.page:
+        ctx.add_message('No duplicate matches found (%d match(es) excluded)' %
+                        ctx.locals.likely.exclude_count)
+    ctx.run_template('dupepersons.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
+    elif ctx.locals.likely.page_process(ctx):
+        return
diff --git a/pages/dupepersons_config.html b/pages/dupepersons_config.html
new file mode 100644
index 0000000..ac0dc66
--- /dev/null
+++ b/pages/dupepersons_config.html
@@ -0,0 +1,133 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Duplicate <al-value expr="config.person_label" /> matching</al-setarg>
+ <table>
+  <td colspan="3">
+   <table class="dupestat" width="100%">
+    <tr>
+     <td>
+      Last scan: <al-value expr="last_dupe" /> (<al-value expr="datetime.relative(last_dupe)" />)
+     </td>
+     <td style="text-align: left;" class="button">
+      <al-input class="butt" type="submit" name="scan:" value="New Scan" />
+     </td>
+     <td style="text-align: left;" class="button">
+      <al-input class="butt" type="submit" name="scan:updated" value="Quick Scan" />
+     </td>
+     <td class="button">
+      <al-input class="butt" type="submit" name="view" value="View Matches" />
+     </td>
+    </tr>
+   </table>
+  </td>
+  <tr>
+   <td colspan="3">
+    N-gram matching occurs between fields within a group. Here you can
+    configure what fields make up those groups, and their relative weights.
+   </td>
+  </tr>
+  <tr>
+   <td valign="top" rowspan="2" width="50%">
+    <table class="dupecfg" border="0" width="100%">
+     <thead>
+      <tr>
+       <th>Group</th>
+       <th>Fields</th>
+       <th> </th>
+      </tr>
+     </thead>
+     <tbody>
+      <al-for iter="ng_i" expr="dupecfg.ngram_groups">
+       <al-exec expr="ng = ng_i.value()" />
+       <al-if expr="ng.enabled"><tr><al-else><tr class="disabled"></al-if>
+        <td class="group">
+         <label><al-value expr="ng.label"></label>
+         <div class="smaller">Weight: 
+          <al-lookup expr="ng.weight">
+           <al-item expr="0.5">Low</al-item>
+           <al-item expr="1.0">Normal</al-item>
+           <al-item expr="2.0">High</al-item>
+           <al-value expr="ng.weight">
+          </al-lookup></div>
+         <al-if expr="not ng.enabled"><div>DISABLED<div></al-if>
+        </td>
+        <td class="fields">
+         <al-for iter="ngf_i" expr="ng.demogfields()">
+          <al-exec expr="ngf = ngf_i.value()" />
+          <al-value expr="ngf.label" /><br>
+         </al-for>
+        </td>
+        <td class="button">
+         <al-input class="butt" type="submit"
+          nameexpr="'edit:%s' % ng_i.index()" value="Edit" />
+        </td>
+       </tr>
+      </al-for>
+     </tbody>
+     <tfoot>
+      <tr>
+       <td colspan="2" class="button"></td>
+       <td class="button">
+         <al-input class="butt" type="submit" name="new" value="New Group" />
+       </td>
+      </tr>
+     <tfoot>
+    </table>
+   </td>
+   <td width="30" rowspan="2"> </td>
+   <td valign="top" align="right" width="50%">
+    <table class="dupecfg" border="0" width="100%" height="100%">
+     <tr>
+      <td class="group"><label for="dupecfg.ngram_level">Text match type</label></td>
+      <td>
+       <al-input type="radio" name="dupecfg.ngram_level" value="2">
+         bigram  
+       <al-input type="radio" name="dupecfg.ngram_level" value="3">
+         trigram  
+      </td>
+     </tr>
+     <tr>
+      <td class="group"><label for="dupecfg.cutoff">Cut-off</label></td>
+      <td>
+       <al-select name="dupecfg.cutoff" optionexpr="dupecfg.cutoff_options" />
+      </td>
+     </tr>
+    </table>
+   </td>
+  </tr>
+  <tr>
+   <td valign="bottom">
+    <table width="100%" class="dupecfg">
+     <tr>
+      <td class="button" style="text-align: left;">
+       <al-input class="butt" type="submit" name="revert" value="Revert" />
+       <al-input class="butt" type="submit" name="reset" value="Defaults" />
+      </td>
+      <td class="button" style="text-align: right;">
+       <al-input class="butt" type="submit" name="save" value="Save" />
+      </td>
+     </tr>
+    </table>
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/dupepersons_config.py b/pages/dupepersons_config.py
new file mode 100644
index 0000000..962590a
--- /dev/null
+++ b/pages/dupepersons_config.py
@@ -0,0 +1,93 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import datetime
+from casemgr import globals, persondupecfg, persondupe
+from pages import page_common
+
+import config
+
+def get_dupecfg(ctx):
+    config = ctx.locals._credentials.prefs.get('persondupecfg')
+    if config is None:
+        config = persondupecfg.new_persondupecfg()
+    return config.start_editing()
+
+
+def save_dupecfg(ctx):
+    ctx.locals.dupecfg.stop_editing()
+    ctx.locals._credentials.prefs.set('persondupecfg', ctx.locals.dupecfg)
+    ctx.locals._credentials.prefs.commit(globals.db, immediate=True)
+    ctx.locals.dupecfg = ctx.locals.dupecfg.start_editing()
+    ctx.add_message('Duplicate %s matching configuration saved' % 
+                    config.person_label)
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_edit(self, ctx, index):
+        ctx.push_page('dupepersons_config_edit', int(index))
+
+    def do_new(self, ctx, ignore):
+        ctx.push_page('dupepersons_config_edit', None)
+
+    def do_reset(self, ctx, ignore):
+        ctx.locals.dupecfg.reset()
+
+    def do_revert(self, ctx, ignore):
+        ctx.locals.dupecfg = get_dupecfg(ctx)
+
+    def do_save(self, ctx, ignore):
+        save_dupecfg(ctx)
+
+    def do_view(self, ctx, ignore):
+        ctx.locals.dupecfg.stop_editing()
+        ctx.locals.last_dupe = persondupe.last_run(globals.db)
+        try:
+            dp = persondupe.loaddupe(globals.db)
+        except persondupe.DupeRunning, m:
+            ctx.add_error(m)
+            # Explicit rollback so page_display can perform TX
+            globals.db.rollback()
+        else:
+            ctx.push_page('dupepersons', dp,
+                          'Possible duplicate %s records' % config.person_label)
+
+    def do_scan(self, ctx, updated_only):
+        save_dupecfg(ctx)
+        persondupe.persondupe(globals.db, ctx.locals.dupecfg,
+                              updated_only=bool(updated_only))
+        ctx.add_message('Cross-check initiated - this will take some time')
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx):
+    # XXX
+    ctx.locals.dupecfg = get_dupecfg(ctx)
+    ctx.locals.last_dupe = persondupe.last_run(globals.db)
+    ctx.add_session_vars('dupecfg', 'last_dupe')
+
+
+def page_leave(ctx):
+    ctx.del_session_vars('dupecfg', 'last_dupe')
+
+
+def page_display(ctx):
+    ctx.run_template('dupepersons_config.html')
+
diff --git a/pages/dupepersons_config_edit.html b/pages/dupepersons_config_edit.html
new file mode 100644
index 0000000..2edc1d1
--- /dev/null
+++ b/pages/dupepersons_config_edit.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Configure duplicate <al-value expr="config.person_label" /> matching</al-setarg>
+ <table class="dupecfg" border="0">
+  <tr>
+   <td class="group"><label for="dupecfg_group.label">Label:</label></td>
+   <td style="padding: 0;">
+    <al-input name="dupecfg_group.label" size="30" >
+   </td>
+  </tr>
+  <tr>
+   <td class="group"><label for="dupecfg_group.enabled">Enabled:</label></td>
+   <td><al-input type="checkbox" name="dupecfg_group.enabled" value="True"></td>
+  </tr>
+  <tr>
+   <td class="group"><label for="dupecfg_group.weight">Weight:</label></td>
+   <td>
+    <al-input type="radio" name="dupecfg_group.weight" value="0.5"> low  
+    <al-input type="radio" name="dupecfg_group.weight" value="1.0"> normal  
+    <al-input type="radio" name="dupecfg_group.weight" value="2.0"> high  
+   </td>
+  </tr>
+  <tr>
+   <td class="group"><label for="dupecfg_group.fields">Fields:</label></td>
+   <al-if expr="dupecfg_group.editable">
+    <td style="padding: 0;">
+     <al-select name="dupecfg_group.fields"  multiple
+       style="width: 100%"
+       sizeexpr="len(dupecfg_group.fields_optionexpr())"
+       optionexpr="dupecfg_group.fields_optionexpr()" />
+    </td>
+   <al-else>
+    <td>not editable for this group.</td>
+   </al-if>
+  </tr>
+  <tr>
+   <td align="left" class="button">
+   </td>
+   <td align="middle" class="button">
+    <al-input type="submit" name="reset" class="butt" value="Defaults" />
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/dupepersons_config_edit.py b/pages/dupepersons_config_edit.py
new file mode 100644
index 0000000..3449079
--- /dev/null
+++ b/pages/dupepersons_config_edit.py
@@ -0,0 +1,45 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import persondupecfg
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_reset(self, ctx, ignore):
+        ctx.locals.dupecfg_group.reset()
+        
+    def do_back(self, ctx, ignore):
+        ctx.locals.dupecfg.apply_group(ctx.locals.dupecfg_group)
+        ctx.pop_page()
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, index):
+    ctx.locals.dupecfg_group = ctx.locals.dupecfg.edit_group(index)
+    ctx.add_session_vars('dupecfg_group')
+
+
+def page_leave(ctx):
+    ctx.del_session_vars('dupecfg_group')
+
+
+def page_display(ctx):
+    ctx.run_template('dupepersons_config_edit.html')
+
diff --git a/pages/error_layout.html b/pages/error_layout.html
new file mode 100644
index 0000000..d58080c
--- /dev/null
+++ b/pages/error_layout.html
@@ -0,0 +1,66 @@
+<al-macro name="error_layout">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<al-comment><!--
+    Note that this template is run with albatross.SimpleContext
+    and consequently most context attributes are missing. See
+    casemgr/handle_exception.py for more details.
+--></al-comment>
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <meta http-equiv="Content-Script-Type" content="text/javascript">
+  <meta name="robots" content="noindex,nofollow">
+  <al-link rel="shortcut icon" hrefexpr="'/%s/images/favicon.ico' % appname" type="image/x-icon">
+  <script type="text/javascript">document.cookie='submit_time=';</script>
+  <style type="text/css" media="all">
+   <!-- @import "/<al-value expr='appname' />/style.css"; -->
+  </style>
+  <title><al-usearg name="title" /> - <al-value expr="apptitle" /></title>
+ </head>
+ <body>
+  <form method="post">
+   <table class="login" cellpadding="0" cellspacing="0" border="0">
+    <tr>
+     <td class="lhs logo">
+      <al-img expr="'/%s/images/netepi-bb.png' % appname" alt="logo" />
+     </td>
+     <td class="rhs">
+      <div class="title">
+       <al-value expr="apptitle" />
+      </div>
+      <div class="subtitle">
+       <al-value expr="subbanner" />
+      </div>
+     </td>
+    </tr>
+    <tr>
+     <td class="lhs"></td>
+     <td class="rhs msg">
+      <al-usearg />
+     </td>
+    </tr>
+   </table>
+  </form>
+ </body>
+</html>
+</al-macro>
diff --git a/pages/export.html b/pages/export.html
new file mode 100644
index 0000000..c9d585b
--- /dev/null
+++ b/pages/export.html
@@ -0,0 +1,139 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="exportset">
+    <td align="center">
+     <al-input type="submit" name="set" class="butt" value="Set" />
+    </td>
+</al-macro>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Export <al-value expr="syndromes[exportsel.syndrome_id].name" /> records</al-setarg>
+  <i class="smaller" style="margin-left: 12px">
+   Note: <b>reports</b> offer a more flexible but slower mechanism for
+   CSV exports of cases and forms, allowing a subset of rows and columns to be
+   exported.</i>
+  <table class="selexp">
+   <tr>
+    <td>Include deleted records?</td>
+    <td>
+     <al-input type="radio" name="exportsel.deleted" 
+      value="n" onchange="submit();" /> No
+     <al-input type="radio" name="exportsel.deleted" 
+      value="both" onchange="submit();" /> Yes
+     <al-input type="radio" name="exportsel.deleted" 
+      value="y" onchange="submit();" /> Only deleted
+    </td>
+    <al-expand name="exportset" />
+   </tr>
+   <tr>
+    <td>Replace newlines in fields with spaces?</td>
+    <td>
+     <al-input type="radio" name="exportsel.strip_newlines" 
+      value="False" onchange="submit();" /> No
+     <al-input type="radio" name="exportsel.strip_newlines" 
+      value="True" onchange="submit();" /> Yes
+    </td>
+    <al-expand name="exportset" />
+   </tr>
+   <al-if expr="exportsel.show_schemes()">
+    <tr>
+     <td>
+      Select export scheme
+     </td>
+     <td>
+      <table cellpadding="0" cellspacing="0" border="0">
+       <al-for iter="i" expr="exportsel.scheme_options()">
+        <tr>
+         <td><al-input type="radio" name="exportsel.export_scheme" 
+           valueexpr="i.value()[0]" onchange="submit();" ></td>
+         <td><al-value expr="i.value()[1]" /></td>
+        </tr>
+       </al-for>
+      </table>
+     </td>
+     <al-expand name="exportset" />
+    </tr>
+   </al-if>
+
+   <al-if expr="exportsel.exporter and exportsel.exporter.forms is not None">
+    <tr>
+     <td valign="top">
+      There <b><al-value expr="exportsel.exporter.entity_info()" /></b> 
+      to export.
+      <al-if expr="len(exportsel.exporter.forms) > 0">
+       <al-if expr="exportsel.multi_form_sel()">
+        What forms would you like to export?
+       <al-else>
+        What form would you like to export?
+       </al-if>
+      <al-else>
+       There are <b>no</b> forms to export.
+      </al-if>
+     </td>
+     <td valign="top">
+      <al-if expr="len(exportsel.exporter.forms) > 0">
+       <table cellpadding="0" cellspacing="0" border="0">
+        <al-for iter="form_i" expr="exportsel.exporter.forms">
+         <al-exec expr="form = form_i.value()">
+         <tr>
+          <td>
+           <al-if expr="exportsel.multi_form_sel()">
+            <al-input type="checkbox" name="exportsel.include_forms" 
+             valueexpr="form.label" list />
+           <al-else>
+            <al-input type="radio" name="exportsel.include_forms" 
+             valueexpr="form.label" />
+           </al-if>
+          </td>
+          <td width="100%">
+           <al-value expr="form.name" />
+          </td>
+         </tr>
+        </al-for>
+       </table>
+      </al-if>
+     </td>
+     <td align="center">
+      <al-if expr="len(exportsel.exporter.forms) > 0 and exportsel.multi_form_sel()">
+       <al-input type="submit" name="select_all" 
+        class="bigbutt" value="Select All" />
+       <br />
+       <al-input type="submit" name="clear_all" 
+        class="bigbutt" value="Clear Selection" />
+      </al-if>
+     </td>
+    </tr>
+   </al-if>
+   <tr>
+    <td colspan="3" class="fauxrule"></td>
+   </tr>
+   <tr>
+    <td align="left">
+    </td>
+    <td></td>
+    <td align="center">
+     <al-if expr="exportsel.exporter">
+      <al-input type="submit" name="doexport" class="butt" value="Export" />
+     </al-if>
+    </td>
+   </tr>
+  </table>
+</al-expand>
diff --git a/pages/export.py b/pages/export.py
new file mode 100644
index 0000000..9c53c34
--- /dev/null
+++ b/pages/export.py
@@ -0,0 +1,57 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+
+from casemgr import dataexport, exportselect
+from pages import page_common
+
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_select_all(self, ctx, ignore):
+        ctx.locals.exportsel.select_all_forms()
+
+    def do_clear_all(self, ctx, ignore):
+        ctx.locals.exportsel.clear_forms()
+
+    def do_doexport(self, ctx, ignore):
+        if ctx.locals.changed:
+            ctx.add_message('Mode has changed, check parameters')
+        else:
+            es = ctx.locals.exportsel
+            page_common.csv_download(ctx, es.row_gen(), es.filename())
+
+    def page_process(self, ctx):
+        ctx.locals.changed = ctx.locals.exportsel.refresh(ctx.locals._credentials)
+        page_common.PageOpsBase.page_process(self, ctx)
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.exportsel = exportselect.ExportSelect(syndrome_id)
+    ctx.locals.exportsel.refresh(ctx.locals._credentials)
+    ctx.add_session_vars('exportsel')
+
+def page_leave(ctx):
+    ctx.del_session_vars('exportsel')
+
+def page_display(ctx):
+    if not page_common.send_download(ctx):
+        ctx.run_template('export.html')
diff --git a/pages/form.html b/pages/form.html
new file mode 100644
index 0000000..ee6f735
--- /dev/null
+++ b/pages/form.html
@@ -0,0 +1,275 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+
+<al-include name="form_inputs.html" />
+
+<al-macro name="form_summary">
+   <al-if expr="not case.is_new()">
+    <al-if expr="case.forms">
+     <table cellspacing="0" class="formslist" cellpadding="1">
+      <al-for iter="form_i" expr="case.forms">
+       <al-exec expr="form = form_i.value()" />
+       <tr>
+        <th colspan="2" class="formname">
+         <al-value expr="form.name" />
+        </th>
+        <th align="center" width="5%">
+         <al-if expr="not case.viewonly()">
+          <al-if expr="form.allow_new_form()">
+           <al-input type="submit" class="butt" value="New"
+            nameexpr="'new:' + form.label" />
+          </al-if>
+         </al-if>
+        </th>
+       </tr>
+       <al-for vars="summary" iter="summary_i" expr="form.summaries">
+        <al-if expr="summary.deleted ">
+         <tr class="deleted">
+          <td class="formid">
+           <al-value expr="form_ui.form_id(summary.summary_id)" /><br>
+          </td>
+          <td width="100%">
+           <al-value expr="summary.delete_timestamp" /> - deleted
+           <al-if expr="summary.delete_reason">
+            - <al-value expr="summary.delete_reason" />
+           </al-if>
+          </td>
+          <td align="center">
+            <al-input type="submit" class="butt" value="View"
+             nameexpr="'edit:%s:%s' % (form.label, summary.summary_id)" /></td>
+         </tr>
+        <al-else>
+         <tr>
+          <td class="formid">
+           <al-value expr="form_ui.form_id(summary.summary_id)" /><br>
+           <al-value expr="summary.form_date.date()" /><br>
+           <al-value expr="summary.form_date.time()" />
+          </td>
+          <td width="100%"><al-value expr="wiki_oneliner(summary.summary)" noescape="noescape" /></td>
+          <td align="center">
+           <al-if expr="not case.viewonly()">
+            <al-input type="submit" class="butt" value="Edit"
+             nameexpr="'edit:%s:%s' % (form.label, summary.summary_id)" />
+           <al-else>
+            <al-input type="submit" class="butt" value="View"
+             nameexpr="'edit:%s:%s' % (form.label, summary.summary_id)" />
+           </al-if>
+          </td>
+         </tr>
+        </al-if>
+       </al-for>
+      </al-for>
+     </table>
+    <al-else>
+     <div class="formlist">No forms available</div>
+    </al-if>
+   </al-if>
+</al-macro>
+
+<al-macro name="paged_form_nav">
+  <tr>
+   <td colspan="3">
+    <table width="100%" border="0">
+     <tr>
+      <td width="15%" align="left">
+       <al-if expr="page > 0">
+        <al-input name="prev_form_page" type="submit" value="Prev Page" />
+       </al-if>
+      </td>
+      <td width="70%" align="center">
+       <al-exec expr="select_form_page = page" />
+       <al-select name="select_form_page" 
+        optionexpr="form.get_toc()" list="list" />
+       <al-input name="goto" type="submit" value="Jump to page" />
+      </td>
+      <td width="15%" align="right">
+       <al-if expr="page < len(form) - 1">
+        <al-input name="next_form_page" type="submit" value="Next Page" />
+       </al-if>
+      </td>
+     </tr>
+    </table>
+   </tr>
+  </tr>
+</al-macro>
+
+<al-macro name="heading">
+  <tr>
+   <td colspan="3" align="center" class="heading">
+    <al-value expr="form.text" />
+    <al-if expr="form_data.summary_id">
+     - <al-value expr="form_ui.form_id(form_data.summary_id)" />
+    </al-if>
+   </td>
+  </tr>
+</al-macro>
+
+
+<al-lookup name="element_markup">
+
+ <!-- Question -->
+ <al-item expr="'Question'">
+  <al-if expr="not node.disabled">
+   <al-exec expr="inputs = node.get_inputs()" />
+   <al-tr classexpr="'question%d' % flipflop" idexpr="'Q_' + node.label">
+    <al-exec expr="flipflop = not flipflop" />
+    <td class="number"><al-value expr="node.label" /></td>
+    <al-if expr="inputs">
+     <td class="qtext">
+    <al-else>
+     <td class="qtext" colspan="2">
+    </al-if>
+     <al-if expr="curnode == node.label and not confirm">
+      <a name="cur" />
+     </al-if>
+     <al-for iter="skiptext_i" expr="node.skiptext()">
+      <div class="skiptext"><al-value expr="wiki_oneliner(skiptext_i.value())" noescape></div>
+     </al-for>
+     <al-if expr="node.help and showhelp != node.label">
+      <div class="infobut">
+       <al-input nameexpr="'showhelp:%s' % node.label.replace('.', '_')" 
+         type="image" srcexpr="appath('images/info.png')" />
+      </div>
+     </al-if>
+     <al-value expr="wiki_text(node.text)" noescape="noescape" />
+     <al-if expr="showhelp == node.label">
+       <div class="info">
+        <div class="infobut">
+         <al-input name="'showhelp_'" 
+           type="image" srcexpr="appath('images/close.png')" />
+        </div>
+        <al-value expr="wiki_text(node.help)" noescape="noescape" />
+       </div>
+     </al-if>
+    </td>
+    <al-if expr="inputs">
+     <td class="inputs">
+      <table width="100%" border="0" cellspacing="0">
+       <al-for iter="input_i" expr="inputs">
+        <al-exec expr="input = input_i.value()">
+        <al-if expr="form_errors.input_has_error(input)">
+         <tbody class="errinp">
+          <tr><td colspan="2" class="error">
+           <al-value expr="form_errors.input_error(input)" />
+          </td></tr>
+        <al-elif expr="input.required">
+         <tbody class="reqinp">
+        <al-else>
+         <tbody>
+        </al-if>
+         <al-value lookup="input_markup" expr="input.render" whitespace />
+         <al-if expr="input_i.index() < len(inputs) - 1">
+          <tr><td colspan="2" class="line"></td></tr>
+         </al-if>
+        </tbody>
+       </al-for>
+      </table>
+     </td>
+    </al-if>
+   </al-tr>
+  </al-if>
+ </al-item>
+
+ <!-- SubSection -->
+ <al-item expr="'SubSection'">
+  <tr>
+   <td class="subsection number">
+    <al-value expr="node.label" />
+   </td>
+   <td class="subsection" colspan="2" width="100%">
+    <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- Section -->
+ <al-item expr="'Section'">
+  <tr class="section">
+   <td class="number">
+    <al-value expr="node.label" />
+   </td>
+   <td class="stext" colspan="2" width="100%">
+    <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- Form -->
+ <al-item expr="'Form'">
+  <al-expand name="heading" />
+ </al-item>
+
+</al-lookup>
+
+<al-macro name="form_page_buttons">
+  <al-if expr="not confirm">
+   <tr>
+    <td colspan="3" class="submit">
+     <table width="100%" border="0">
+      <tr>
+       <td align="left">
+        <al-if expr="not form_disabled">
+         <al-input name="form_cancel" type="submit" class="butt"
+          value="Cancel" />
+        <al-else>
+         <al-input name="form_cancel" type="submit" class="butt"
+          value="<< Back" />
+        </al-if>
+       </td>
+       <td align="center">
+        <al-if expr="not form_disabled">
+         <al-input name="form_delete" type="submit" class="butt"
+          value="Delete Form" />
+        </al-if>
+       </td>
+       <td align="right">
+        <al-if expr="not form_disabled">
+         <al-input name="form_submit" type="submit" class="butt"
+          value="Save Form" />
+         <script>enterSubmit('appform', 'form_submit');</script>
+        </al-if>
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </al-if>
+</al-macro>
+
+<al-macro name="form_page">
+
+ <table border="0" cellspacing="0" cellpadding="0" id="smartform">
+  <al-expand name="form_page_buttons" />
+  <al-exec expr="flipflop = False" />
+  <al-tree iter="tree_i" expr="form">
+   <al-exec expr="node = tree_i.value()" />
+   <al-value lookup="element_markup" expr="node.render" whitespace />
+   <tr><td colspan="3" class="line"></td></tr>
+  </al-tree>
+  <tr><td colspan="3" class="fauxrule"></td></tr>
+  <al-expand name="form_page_buttons" />
+ </table>
+ <al-if expr="has_js == 'yes' and not form_disabled">
+  <al-script srcexpr="appath('formhelpers.js')" type="text/javascript"></al-script>
+  <script type="text/javascript"><al-value expr="form.js_meta(form_errors).to_js()" noescape /></script>
+ </al-if>
+</al-macro>
+
diff --git a/pages/form_inputs.html b/pages/form_inputs.html
new file mode 100644
index 0000000..fde0251
--- /dev/null
+++ b/pages/form_inputs.html
@@ -0,0 +1,169 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="input_pre_text">
+       <al-exec expr="pre_text = input.get_pre_text()" />
+       <al-if expr="pre_text">
+        <tr><td class="pretext" colspan="2">
+         <al-value expr="wiki_oneliner(pre_text)" noescape="noescape" />
+        </td></tr>
+       </al-if>
+</al-macro>
+
+<al-macro name="input_post_text">
+       <al-exec expr="post_text = input.get_post_text()" />
+       <al-if expr="post_text">
+        <tr><td class="posttext" colspan="2">
+         <al-value expr="wiki_oneliner(post_text)" noescape="noescape" />
+        </td></tr>
+       </al-if>
+</al-macro>
+
+<al-macro name="input_skipon">
+       <al-exec expr="skips = input.skiptext()" />
+       <al-if expr="skips">
+        <al-for iter="skip_i" expr="skips">
+         <tr><td class="skiptext" colspan="2">
+          <al-value expr="wiki_oneliner(skip_i.value())" noescape="noescape" />.
+         </td></tr>
+        </al-for>
+       </al-if>
+</al-macro>
+
+<al-lookup name="input_markup">
+
+ <!-- RadioList -->
+ <al-item expr="'RadioList'">
+  <al-expand name="input_pre_text" />
+  <al-if expr="input.render_horizontal()">
+   <tr>
+    <td valign="baseline" width="100%" nowrap colspan="2">
+     <al-for iter="choice_i" expr="input.get_choices()">
+      <al-exec expr="choice = choice_i.value()" />
+      <al-input type="radio" disabledbool="form_disabled"
+       nameexpr="'form_data.' + input.get_column_name()"
+       valueexpr="choice[0]" /> <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" />  
+     </al-for>
+    </td>
+   </tr>
+  <al-else>
+   <al-for iter="choice_i" expr="input.get_choices()">
+    <al-exec expr="choice = choice_i.value()" />
+    <tr>
+     <td valign="baseline" width="16">
+      <al-input type="radio" disabledbool="form_disabled"
+       nameexpr="'form_data.' + input.get_column_name()"
+       valueexpr="choice[0]" />
+     </td>
+     <td valign="baseline" width="100%">
+      <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" />
+     </td>
+    </tr>
+   </al-for>
+  </al-if>
+  <al-expand name="input_skipon" />
+  <al-expand name="input_post_text" />
+ </al-item>
+
+ <!-- DropList -->
+ <al-item expr="'DropList'">
+  <al-expand name="input_pre_text" />
+  <tr>
+   <td colspan="2">
+    <al-select disabledbool="form_disabled"
+     nameexpr="'form_data.' + input.get_column_name()" 
+     optionexpr="input.get_choices()" />
+   </td>
+  </tr>
+  <al-expand name="input_skipon" />
+  <al-expand name="input_post_text" />
+ </al-item>
+
+ <!-- TextInput -->
+ <al-item expr="'TextInput'">
+  <al-expand name="input_pre_text" />
+  <tr>
+   <td colspan="2">
+    <al-input type="text" disabledbool="form_disabled"
+     nameexpr="'form_data.' + input.get_column_name()" size="30" />
+   </td>
+  </tr>
+  <al-expand name="input_post_text" />
+ </al-item>
+
+ <!-- DateInput -->
+ <al-item expr="'DateInput'">
+  <al-expand name="input_pre_text" />
+  <tr>
+   <td colspan="2" nowrap>
+    <al-input type="text" disabledbool="form_disabled" size="30"
+     calendarformatexpr="input.format()"
+     nameexpr="'form_data.' + input.get_column_name()"
+     idexpr="'form_data.' + input.get_column_name()" />
+   </td>
+  </tr>
+  <al-expand name="input_post_text" />
+ </al-item>
+
+ <!-- TextArea -->
+ <al-item expr="'TextArea'">
+  <al-expand name="input_pre_text" />
+  <tr>
+   <td colspan="2">
+    <al-textarea disabledbool="form_disabled"
+     nameexpr="'form_data.' + input.get_column_name()" rows="5" cols="40" />
+   </td>
+  </tr>
+  <al-expand name="input_post_text" />
+ </al-item>
+
+ <!-- CheckBoxes -->
+ <al-item expr="'CheckBoxes'">
+  <al-expand name="input_pre_text" />
+  <al-if expr="input.render_horizontal()">
+   <tr>
+    <td valign="baseline" width="100%" nowrap colspan="2">
+     <al-for iter="choice_i" expr="input.get_choices()">
+      <al-exec expr="choice = choice_i.value()" />
+      <al-input type="checkbox" disabledbool="form_disabled"
+       nameexpr="'form_data.' + input.get_column_name() + choice[0].lower()"
+       value="True" /> <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" />  
+     </al-for>
+    </td>
+   </tr>
+  <al-else>
+   <al-for iter="choice_i" expr="input.get_choices()">
+    <al-exec expr="choice = choice_i.value()" />
+    <tr>
+     <td valign="baseline" width="16">
+      <al-input type="checkbox" disabledbool="form_disabled"
+       nameexpr="'form_data.' + input.get_column_name() + choice[0].lower()"
+       value="True" />
+     </td>
+     <td valign="baseline" width="100%">
+      <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" />
+     </td>
+    </tr>
+   </al-for>
+  </al-if>
+  <al-expand name="input_skipon" />
+  <al-expand name="input_post_text" />
+ </al-item>
+</al-lookup>
diff --git a/pages/group_edit.html b/pages/group_edit.html
new file mode 100644
index 0000000..e476afc
--- /dev/null
+++ b/pages/group_edit.html
@@ -0,0 +1,42 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="group_edit">
+ <table width="100%" class="pt-sel darker">
+  <tr>
+   <th width="50%">Selected</th>
+   <th width="50%">Available</th>
+  </tr>
+  <tr>
+   <td>
+    <al-select name="group_edit.exclude_group" size="5" multiple
+     optionexpr="group_edit.get_included()" /><br />
+    <al-input class="butt" type="submit" value="Remove >>"
+     nameexpr="'%s:remove' % group_edit.name">
+   </td>
+   <td>
+    <al-select name="group_edit.include_group" size="5" multiple
+     optionexpr="group_edit.get_available()" /><br />
+    <al-input class="butt" type="submit" value="<< Add"
+     nameexpr="'%s:add' % group_edit.name">
+   </td>
+  </tr>
+ </table>
+</al-macro>
diff --git a/pages/login.html b/pages/login.html
new file mode 100644
index 0000000..a8972cd
--- /dev/null
+++ b/pages/login.html
@@ -0,0 +1,131 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout">
+  <al-expand name="render_msgs" />
+  <al-setarg name="title">Login</al-setarg>
+   <al-input name="has_js" type="hidden" />
+   <script>
+    <!--
+     document.forms[0].has_js.value = "yes";
+    -->
+   </script>
+
+   <table class="login" cellpadding="0" cellspacing="0" border="0">
+    <al-if expr="via_logout">
+     <tr>
+      <td class="lhs"> </td>
+      <td class="rhs logout">
+       Thank you for using <al-value expr="apptitle" />. Your session
+       is now closed.  Please note: if you are using <al-value
+       expr="apptitle" /> from an untrusted computer (one that is
+       shared or in a public space) you should clear the browser's
+       cache and close the browser to ensure that others cannot see
+       any details of the data you may have entered or reviewed.
+      </td>
+     </tr>
+    </al-if>
+    <tr>
+     <td class="lhs logo">
+      <al-img expr="appath('images/netepi-bb.png')" 
+        alt="NetEpi Collection logo" />
+     </td>
+     <td class="rhs">
+      <div class="title">
+       <al-value expr="config.apptitle" />
+      </div>
+      <div class="subtitle">
+       <al-value expr="config.subbanner" />
+      </div>
+     </td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs"> </td><td class="rhs"> </td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs">
+      <label for="username">User name</label>
+     </td>
+     <td class="rhs">
+      <al-input type="text" name="username" id="username" 
+        disabledbool="unit_options" tabindex="1">
+     </td>
+    </tr>
+    <tr class="prompt">
+     <td class="lhs">
+      <label for="password">Password</label>
+     </td>
+     <td class="rhs">
+      <al-input name="password" type="password" id="password" 
+        disabledbool="unit_options" tabindex="2">
+     </td>
+    </tr>
+    <al-if expr="unit_options">
+     <tr class="prompt">
+      <td class="lhs">
+       <label for="unit"><al-value expr="config.unit_label" /></label>
+      </td>
+      <td class="rhs">
+        <al-select name="unit_id" id="unit" tabindex="3" 
+           optionexpr="unit_options" />
+      </td>
+     </tr>
+    </al-if>
+    <tr class="prompt">
+     <td class="lhs"> </td>
+     <td class="rhs">
+      <al-input type="submit" name="login" value="Log in" tabindex="4">
+     </td>
+    </tr>
+    <al-if expr="config.user_registration_mode != 'none' and not unit_options">
+     <tr class="apply prompt">
+      <td class="lhs"> </td>
+      <td class="rhs">
+        <al-input type="submit" name="register" value="Apply for a user account" >
+      </td>
+     </tr>
+    </al-if>
+    <tr class="help">
+     <td class="lhs">
+      <al-a class="help" target="_blank" expr="appath('help/help.html#' + __page__)"><al-img expr="appath('images/help-w.png')" border="0" alt="Help" /></al-a>
+     </td>
+     <td class="rhs">
+      <al-if expr="config.login_helpdesk_contact">
+       For assistance, please contact
+       <al-value expr="config.login_helpdesk_contact" noescape />.
+      </al-if>
+     </td>
+    </tr>
+    <tr class="dedication">
+     <td class="lhs"> </td>
+     <td class="rhs">
+      Dedicated to the memory of Professor Aileen Plant, 1948-2007.<br>
+      <a href="http://memorial.curtin.edu.au/aileenplant/">[1]</a> <a href="http://www.smh.com.au/news/obituaries/she-fought-disease-despite-risks/2007/04/15/1176575678645.html?page=fullpage">[2]</a> <a href="http://en.wikipedia.org/wiki/Aileen_Plant">[3]</a> <a href="http://www.publish.csiro.au/view/journals/dsp_journal_fulltext.cfm?nid=226&f=NB07067">[4]</a> <a href="http://www.who.int/mediacentre/news/statements/2007/s07/en/index.html">[5]</a> <a href="http://www.mja.com.au/public [...]
+      <div class="copyright">
+       This web site uses <a href="http://www.netepi.org/">NetEpi Collection</a> version <al-value expr="__version__" /> <al-value expr="__svnrev__" /><br>
+       <al-a target="_blank" expr="appath('help', 'copyright.html')">
+       Copyright ©</al-a> 2004-2010 NSW Department of Health,
+       Australian Government Department of Health and Ageing, and others
+       and available under a free, open-source license.<br>
+      </div>
+     </td>
+    </tr>
+   </table>
+</al-expand>
diff --git a/pages/login.py b/pages/login.py
new file mode 100644
index 0000000..3a7d206
--- /dev/null
+++ b/pages/login.py
@@ -0,0 +1,74 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import credentials, globals, user_edit
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_login(self, ctx, ignore):
+        cred = ctx.locals._credentials
+        try:
+            cred.authenticate_user(globals.db, ctx.locals.username, 
+                                    ctx.locals.password)
+        except credentials.DisabledUser, e:
+            ctx.msg('warn', '%s. You may update your contact details '
+                            'if necessary:' % e)
+            ue = user_edit.DisabledEdit(None, e.user.user_id)
+            ctx.push_page('useredit', ue)
+        except credentials.SelectUnit:
+            ctx.set_page('unitselect')
+        else:
+            ctx.set_page('main')
+
+    def do_register(self, ctx, ignore):
+        if config.user_registration_mode == 'register':
+            ue = user_edit.SelfRegister(None)
+        elif config.user_registration_mode in ('invite', 'sponsor'):
+            ue = user_edit.SponsoredRegister(None)
+        else:
+            return
+        ctx.push_page('useredit', ue)
+
+    def do_invite(self, ctx, key):
+        ue = user_edit.SponsoredRegister(None, key)
+        ctx.push_page('useredit', ue)
+
+pageops = PageOps()
+
+class DummyUnit:
+    name = '----'
+
+def page_enter(ctx):
+    ctx.locals.has_js = ''
+    ctx.add_session_vars('has_js')
+    ctx.locals.confirm = None
+    ctx.add_session_vars('confirm')
+    ctx.locals._credentials = credentials.Credentials()
+    ctx.add_session_vars('_credentials')
+
+def page_display(ctx):
+    ctx.locals.password = ''
+    ctx.locals.unit_options = None
+    ctx.set_save_session(False)
+    ctx.run_template('login.html')
+
+def page_process(ctx):
+    ctx.merge_vars('invite')
+    pageops.page_process(ctx)
diff --git a/pages/logview.html b/pages/logview.html
new file mode 100644
index 0000000..aaf567a
--- /dev/null
+++ b/pages/logview.html
@@ -0,0 +1,73 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="paged_search.title" /></al-setarg>
+  <table border="0" class="gridtab">
+   <al-for iter="h" expr="paged_search.cols()">
+    <al-if expr="h.value().wide">
+     <col align="left" width="100%">
+    <al-else>
+     <col align="center">
+    </al-if>
+   </al-for>
+   <thead>
+    <tr>
+     <al-td colspanexpr="paged_search.ncols()">
+      <al-expand name="page_select" />
+     </al-td>
+    </tr>
+    <tr>
+     <al-expand name="sort_header" />
+    </tr>
+   </thead>
+   <tfoot>
+    <tr>
+     <al-td colspanexpr="paged_search.ncols()">
+      <al-expand name="page_select" />
+     </al-td>
+    </tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="log_i" expr="paged_search.result_page()">
+     <al-exec expr="log = log_i.value()" />
+     <al-if expr="log_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <al-for iter="h" expr="paged_search.cols()">
+       <al-exec expr="col = h.value()" />
+       <al-td nowrapbool="not col.wide">
+        <al-value expr="col.pretty_row(log)" />
+       </al-td>
+      </al-for>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <al-td colspanexpr="len(paged_search.headers)" class="reverr">
+       <al-value expr="paged_search.error" />
+      </al-td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
diff --git a/pages/logview.py b/pages/logview.py
new file mode 100644
index 0000000..3db02e4
--- /dev/null
+++ b/pages/logview.py
@@ -0,0 +1,34 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import paged_search
+from pages import page_common
+
+import config
+
+def page_enter(ctx, logview):
+    paged_search.push_pager(ctx, logview)
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+
+def page_display(ctx):
+    ctx.run_template('logview.html')
+
+page_process = page_common.page_process
+    
diff --git a/pages/main.html b/pages/main.html
new file mode 100644
index 0000000..0846cf0
--- /dev/null
+++ b/pages/main.html
@@ -0,0 +1,153 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="syndlist.html" />
+<al-include name="bulletin_list.html" />
+
+<al-macro name="userdetails">
+    <table class="userdetails">
+     <tr>
+      <td align="left">
+       <label>Logged in as</label>
+       <al-value expr="_credentials.user.fullname">
+      </td>
+      <td align="center">
+       <label><al-value expr="config.group_label" /></label>
+       <al-value expr="', '.join(_credentials.unit.groups)" />
+      </td>
+      <td align="right">
+       <label><al-value expr="config.unit_label" /></label>
+       <al-if expr="len(_credentials.unit_options) > 1">
+        <al-select name="unit_select" onchange="submit();"
+         optionexpr="_credentials.unit_options" />
+        <al-if expr="not has_js">
+         <al-input type="submit" class="smallbutt" 
+          name="changeunit" value="Change" />
+        </al-if>
+       <al-else>
+        <al-value expr="_credentials.unit.name" />
+       </al-if>
+      </td>
+     </tr>
+    </table>
+</al-macro>
+
+<al-macro name="top-tasks">
+       <table id="top-tasks" class="top-tasks">
+        <tr>
+         <th>Task</th>
+         <th>Due</th>
+        </tr>
+        <al-for vars="qt" expr="quick_tasks">
+         <al-tr idexpr="'task:%s' % qt.task_id" class="clickable">
+          <td>
+           <al-value expr="qt.surname" />, 
+           <al-value expr="qt.given_names" /> 
+           (ID <al-value expr="qt.case_id" />)
+           <div class="desc"><al-value expr="qt.description" /></div>
+          </td>
+          <td>
+           <al-value expr="qt.due_relative" />
+          </td>
+         </al-tr>
+        </al-for>
+       </table>
+       <script>clicktab('top-tasks', 'appform');</script>
+</al-macro>
+
+<al-macro name="recent-activity">
+       <table id="recent-activity" class="recent-activity">
+        <tr><th>Recently accessed</th></tr>
+        <al-for vars="id, label" expr="_credentials.prefs.get_recent_cases()">
+         <al-tr idexpr="'recent_case:%s' % id" class="clickable">
+          <td><al-value expr="label" /></td>
+         </al-tr>
+        </al-for>
+       </table>
+       <script>clicktab('recent-activity', 'appform');</script>
+</al-macro>
+
+<al-macro name="banner-tabs">
+       <div class="banner-tabs">
+        <al-for vars="tab" expr="page_common.bannertabs(__ctx__)">
+         <al-input type="submit" nameexpr="tab.name" expr="tab.name" />
+        </al-for>
+       </div>
+</al-macro>
+
+<al-macro name="main_rhs">
+  <al-expand name="userdetails" />
+
+  <al-expand name="render_msgs" />
+
+  <al-if expr="helpdesk_contact">
+    <div class="helpdesk">
+     For assistance, please contact 
+     <al-value expr="helpdesk_contact" noescape />.
+    </div>
+  </al-if>
+
+  <al-expand name="syndlist" />
+
+  <table class="twocols">
+   <tr>
+    <td class="leftcol">
+     <al-expand name="top-tasks" />
+    </td>
+    <td class="rightcol">
+     <al-expand name="recent-activity" />
+    </td>
+   </tr>
+  </table>
+
+  <al-expand name="debug_box" />
+</al-macro>
+
+<al-expand name="page_layout">
+ <al-setarg name="title">Home</al-setarg>
+
+ <al-if expr="has_js == 'yes'">
+  <al-input type="hidden" name="action" value="" />
+ </al-if>
+
+ <al-expand name="banner_box">
+  <al-setarg name="banner_extra">
+   <td>
+    <al-input class="quick-search" name="quick_search" id="quick_search" onfocus="this.style.width='16em'" onblur="this.style.width='8em'" />
+    <script>inputHint('quick_search', 'Quick search...');</script>
+   </td>
+  </al-setarg>
+ </al-expand>
+
+
+ <al-if expr="bulletin_list">
+  <div class="left20">
+   <al-expand name="bulletin_list" />
+  </div>
+
+  <div class="right80">
+   <al-expand name="main_rhs" />
+  </div>
+ <al-else>
+  <al-expand name="main_rhs" />
+ </al-if>
+
+
+</al-expand>
diff --git a/pages/main.py b/pages/main.py
new file mode 100644
index 0000000..d367fb7
--- /dev/null
+++ b/pages/main.py
@@ -0,0 +1,226 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import time
+
+from cocklebur import dbobj
+
+from casemgr import globals, cases, syndrome, bulletins, search,\
+                    caseset, tasksearch, reports
+
+from pages import page_common, report_ops, search_ops, taskaction
+
+import config
+
+class SyndItem:
+    """
+    Represents a syndrome listed on the main page, with associated
+    buttons and styling.
+    """
+    def __init__(self, syndromes, syndrome, cred):
+        def report_options(menuitem, syndrome_id):
+            reports_c = reports.reports_cache.get_synd_unit(syndrome_id, 
+                                                            cred.unit.unit_id)
+            for report in reports_c:
+                name = 'report:%s:%s' % (report.report_params_id, 
+                                         syndrome.syndrome_id)
+                menuitem.add_drop(name, report.label)
+        self.syndrome = syndrome
+        self.syndrome_id = id = syndrome.syndrome_id
+        self.record_count = syndrome.case_count
+        view_only = 'VIEWONLY' in cred.rights
+        self.menubar = page_common.MenuBar()
+        if not view_only and syndromes.can_add(id):
+            self.menubar.add_left('add:%s' % id, 'add')
+        self.menubar.add_left('search:%s' % id, 'search')
+        show_reports = cred.rights.any('EXPORT', 'PUBREP')
+        if show_reports:
+            menuitem = self.menubar.add_left('reports:%s' % id, 'reports')
+            report_options(menuitem, id)
+        menuitem = self.menubar.add_left('other:%s' % id, 'other actions',
+                                         style='')
+        menuitem.add_drop('printforms:%s' % id, 'Print blank forms')
+        if 'EXPORT' in cred.rights:
+            menuitem.add_drop('export:%s' % id, 'Export records')
+        if 'IMPORT' in cred.rights:
+            menuitem.add_drop('import:%s' % id, 'Import data')
+
+
+def gen_synd_list(syndromes, credentials):
+    return [SyndItem(syndromes, synd, credentials) for synd in syndromes]
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_admin(self, ctx, ignore):
+        if 'ADMIN' in ctx.locals._credentials.rights:
+            ctx.push_page('admin')
+
+    def do_tools(self, ctx, ignore):
+        ctx.push_page('tools')
+
+    def do_query_tasks(self, ctx, ignore):
+        ctx.push_page('tasks')
+
+    def do_syn_detail(self, ctx, syndrome_id):
+        ctx.push_page('syn_detail', int(syndrome_id))
+
+    def hide_bulletins(self, ctx):
+        ctx.locals._credentials.prefs.set('bulletin_time', time.time())
+
+    def do_bulletin_detail(self, ctx, bulletin_id):
+        if bulletin_id.lower() == 'hide':
+            self.hide_bulletins(ctx)
+        else:
+            ctx.push_page('bulletin_detail', int(bulletin_id))
+
+    def do_hide_bulletins(self, ctx, ignore):
+        self.hide_bulletins(ctx)
+
+    def do_show_bulletins(self, ctx, ignore):
+        ctx.locals._credentials.prefs.set('bulletin_time', None)
+
+    # Not used by current home page
+    #def do_search(self, ctx, ignore):
+    #    ctx.push_page('search', search_ops.SearchOps(ctx.locals._credentials))
+
+    def do_search(self, ctx, syndrome_id):
+        ctx.push_page('search', search_ops.EditOps(ctx.locals._credentials, 
+                                                   int(syndrome_id)))
+
+    def do_quick_search(self, ctx, quick_search):
+        if quick_search:
+            ctx.locals.quick_search = None
+            so = search_ops.SearchOps(ctx.locals._credentials)
+            ctx.locals.search = search.Search(so)
+            ctx.locals.search.quicksearch = quick_search
+            ctx.locals.search.search(ctx.locals)
+            ctx.locals.search.search_ops.result(ctx, ctx.locals.search.result)
+
+    def do_add(self, ctx, syndrome_id):
+        ctx.push_page('search', search_ops.NewCaseOps(ctx.locals._credentials, 
+                                                      int(syndrome_id)))
+
+    # Extra syndrome actions
+    def do_reports(self, ctx, syndrome_id):
+        if ctx.locals._credentials.rights.any('EXPORT', 'PUBREP'):
+            ctx.push_page('report_menu', int(syndrome_id))
+
+    def do_report(self, ctx, report_id, syndrome_id):
+        if ctx.locals._credentials.rights.any('EXPORT', 'PUBREP'):
+            reportparams = reports.load(int(report_id),
+                                        ctx.locals._credentials)
+            report_ops.run_report(ctx, reportparams)
+
+    def do_printforms(self, ctx, syndrome_id):
+        ctx.push_page('selprintforms', int(syndrome_id))
+
+    def do_export(self, ctx, syndrome_id):
+        if 'EXPORT' in ctx.locals._credentials.rights:
+            ctx.push_page('export', int(syndrome_id))
+
+    def do_import(self, ctx, syndrome_id):
+        if 'IMPORT' in ctx.locals._credentials.rights:
+            ctx.push_page('dataimp', int(syndrome_id))
+
+    # Swine Flu special
+    def do_crosstab_counts(self, ctx, syndrome_id):
+        ctx.push_page('crosstab_counts', syndrome_id, None)
+
+    def do_crosstab_counts_params(self, ctx, syndrome_id):
+        ctx.push_page('crosstab_counts_params', syndrome_id)
+
+    def do_recent_case(self, ctx, case_id):
+        page_common.go_id(ctx, case_id)
+
+    def do_task(self, ctx, task_id):
+        try:
+            taskaction.task_dispatch(ctx, int(task_id), ctx.push_page)
+        except taskaction.TAError, e:
+            ctx.add_error(e)
+        
+
+pageops = PageOps()
+
+
+def cred_init(ctx):
+    cred = ctx.locals._credentials
+    ctx.locals.syndromes = syndrome.UnitSyndromesView(cred)
+    cred.prefs.set('current_unit', cred.unit.unit_id)
+
+
+def page_enter(ctx):
+    cred = ctx.locals._credentials
+    if cred.prefs.lost_reason:
+        ctx.add_error('Unable to restore user preferences (%s)' % 
+                        cred.prefs.lost_reason)
+        cred.prefs.lost_reason = None
+    for msg in cred.messages:
+        ctx.add_message(msg)
+    del cred.messages[:]
+    cred_init(ctx)
+    ctx.locals.bulletins = bulletins.Bulletins(globals.db, cred)
+    ctx.locals.casesets = caseset.CaseSets(cred)
+    ctx.locals.quick_tasks = tasksearch.QuickTasks(ctx.locals._credentials)
+    ctx.locals.task = None
+    ctx.locals.case = None
+    ctx.locals.search = None
+    ctx.locals.caseset = None
+    ctx.locals.double_trap = False
+    ctx.add_session_vars('syndromes', 'bulletins', 'casesets', 'quick_tasks',
+                         'task', 'case', 'search', 'caseset', 'double_trap')
+
+
+def page_display(ctx):
+    hide_time = ctx.locals._credentials.prefs.get('bulletin_time')
+    ctx.locals.bulletin_list = ctx.locals.bulletins.get_bulletins(hide_time)
+    # User may have aborted via the "home" button - release stuff...
+    page_common.idle(ctx)
+    ctx.locals.synd_list = gen_synd_list(ctx.locals.syndromes,
+                                         ctx.locals._credentials)
+    ctx.locals.unit_select = ctx.locals._credentials.unit.unit_id
+    ctx.locals.quick_tasks.refresh()
+    ctx.run_template('main.html')
+
+
+def page_process(ctx):
+    ctx.locals.double_trap = False
+    cred = ctx.locals._credentials
+    # Does the user wish to change units?
+    try:
+        unit_select = int(ctx.locals.unit_select)
+    except (ValueError, AttributeError):
+        pass
+    else:
+        if unit_select != cred.unit.unit_id:
+            cred.set_unit(globals.db, unit_select)
+            cred_init(ctx)
+
+    # Any "More options..." selections?
+    syndextra = getattr(ctx.locals, 'syndextra', [])
+    ctx.locals.syndextra = []
+    for op in syndextra:
+        if op:
+            fields = op.split(':')
+            meth = getattr(pageops, 'do_%s' % fields[0], None)
+            if meth:
+                try:
+                    return meth(ctx, *map(int, fields[1:]))
+                except pageops.app_errors, e:
+                    globals.db.rollback()
+                    ctx.add_error(e)
+    # Otherwise, defer to normal page dispatch
+    pageops.page_process(ctx)
diff --git a/pages/manualdupe.html b/pages/manualdupe.html
new file mode 100644
index 0000000..41eb435
--- /dev/null
+++ b/pages/manualdupe.html
@@ -0,0 +1,43 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Manually merge <al-value expr="config.person_label" /> records associated with Cases</al-setarg>
+ <table class="widelabelform">
+  <tr>
+   <td class="label">
+    <label for="case_id_a">First <al-value expr="case_id_label" /></label></td>
+   <td><al-input name="case_id_a" /></td>
+  </tr>
+  <tr>
+   <td class="label">
+    <label for="case_id_b">Second <al-value expr="case_id_label" /></label></td>
+   <td><al-input name="case_id_b" /></td>
+  </tr>
+  <tr>
+   <td align="left">
+   </td>
+   <td align="right">
+    <al-input name="showmerge" type="submit" class="butt" value="Show Merge" />
+    <script>enterSubmit('appform', 'showmerge');</script>
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/manualdupe.py b/pages/manualdupe.py
new file mode 100644
index 0000000..d3f078c
--- /dev/null
+++ b/pages/manualdupe.py
@@ -0,0 +1,56 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, persondupe, personmerge, demogfields
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_showmerge(self, ctx, ignore):
+        case_id_a, case_id_b = ctx.locals.case_id_a, ctx.locals.case_id_b
+        try:
+            case_id_a = int(case_id_a)
+        except ValueError:
+            raise page_common.PageError('Invalid ID: %s' % case_id_a)
+        try:
+            case_id_b = int(case_id_b)
+        except ValueError:
+            raise page_common.PageError('Invalid ID: %s' % case_id_b)
+        merge = personmerge.merge_case_persons(case_id_a, case_id_b)
+        ctx.push_page('mergeperson', merge)
+
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    persondupe.dupe_lock(globals.db)
+
+def page_leave(ctx):
+    pass
+
+def page_display(ctx):
+    fields = demogfields.get_demog_fields(globals.db, None)
+    ctx.locals.case_id_label = fields.field_by_name('case_id').label
+    ctx.run_template('manualdupe.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
+
diff --git a/pages/mergecase.html b/pages/mergecase.html
new file mode 100644
index 0000000..49fbb9b
--- /dev/null
+++ b/pages/mergecase.html
@@ -0,0 +1,119 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge records</al-setarg>
+ <al-exec expr="field_rows = selcasemerge.personfields_rows_and_cols()" />
+ <al-expand name="demogfields_text" />
+
+  <table class="duperes">
+  <thead>
+   <tr class="top">
+    <td align="right">Source:</td>
+    <th style="min-width: 16em;">A
+     <al-if expr="casemerge.case_a.deleted">
+      <br>DELETED
+      <al-if expr="casemerge.case_a.delete_reason"> - <span class="smaller"><al-value expr="casemerge.case_a.delete_reason" /></span></al-if>
+     </al-if>
+    </th>
+    <th style="min-width: 16em;">B
+     <al-if expr="casemerge.case_b.deleted">
+      <br>DELETED
+      <al-if expr="casemerge.case_b.delete_reason"> - <span class="smaller"><al-value expr="casemerge.case_b.delete_reason" /></span></al-if>
+     </al-if>
+    </th>
+    <th>Edit</th>
+    <th>Clear</th>
+   </tr>
+  </thead>
+  <tfoot>
+   <tr>
+    <td colspan="5">
+     <table width="100%">
+      <tr>
+       <td align="right">
+        <al-input name="showmerge" type="submit" class="butt" 
+          value="Show Merge" />
+        <script>enterSubmit('appform', 'showmerge');</script>
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </tfoot>
+  <tbody>
+   <tr>
+    <th class="label">
+     <al-value expr="casemerge.demogfield('case_id').label" />
+    </th>
+    <al-td classexpr="casemerge.css_a">
+     <al-input type="radio" value="a" name="casemerge.keep" />
+     <al-value expr="casemerge.case_a.case_id" />
+    </al-td>
+    <al-td classexpr="casemerge.css_b">
+     <al-input type="radio" value="b" name="casemerge.keep" />
+     <al-value expr="casemerge.case_b.case_id" />
+    </al-td>
+    <td></td>
+    <td></td>
+   </tr>
+   <al-for iter="f_i" expr="casemerge.fields">
+    <al-exec expr="mc = f_i.value()" />
+    <al-if expr="mc.conflict"><tr class="warn"><al-else><tr></al-if>
+     <th class="label">
+      <al-label><al-value expr="mc.label" /></al-label>
+     </th>
+     <al-td classexpr="casemerge.css_a">
+      <al-exec expr="value_a = mc.outtrans(casemerge.case_a)" />
+      <al-if expr="value_a">
+       <al-if expr="mc.show_radio">
+        <al-input type="radio" value="a" nameexpr="mc.source_field" />
+       </al-if>
+       <al-value expr="value_a" />
+      </al-if>
+     </al-td>
+     <al-td classexpr="casemerge.css_b">
+      <al-exec expr="value_b = mc.outtrans(casemerge.case_b)" />
+      <al-if expr="value_b">
+       <al-if expr="mc.show_radio">
+        <al-input type="radio" value="b" nameexpr="mc.source_field" />
+       </al-if>
+       <al-value expr="value_b" />
+      </al-if>
+     </al-td>
+     <td class="user">
+      <al-if expr="mc.field is not None">
+       <al-exec expr="field = mc.field" />
+       <al-value lookup="field_render" expr="field.render" />
+      </al-if>
+     </td>
+     <td align="center">
+      <al-if expr="mc.field is not None">
+       <al-if expr="mc.show_radio">
+        <al-input type="radio" value="d" nameexpr="mc.source_field" /></td>
+       </al-if>
+      </al-if>
+    </tr>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/mergecase.py b/pages/mergecase.py
new file mode 100644
index 0000000..b7014c0
--- /dev/null
+++ b/pages/mergecase.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, casemerge
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_showmerge(self, ctx, ignore):
+        ctx.push_page('mergecase_detail')
+
+    def do_tagbrowse(self, ctx, ignore):
+        ctx.push_page('tagbrowse', 'Merged case tags', 'casemerge.case.tags')
+    
+
+pageops = PageOps()
+
+def page_enter(ctx, casemerge):
+    ctx.locals.casemerge = casemerge
+    ctx.add_session_vars('casemerge')
+
+def page_leave(ctx):
+    ctx.del_session_vars('casemerge')
+
+def page_display(ctx):
+    ctx.run_template('mergecase.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/mergecase_detail.html b/pages/mergecase_detail.html
new file mode 100644
index 0000000..93f6a46
--- /dev/null
+++ b/pages/mergecase_detail.html
@@ -0,0 +1,54 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+<al-include name="demogfields.html" />
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge record - proposed actions</al-setarg>
+ <al-exec expr="field_rows = selcasemerge.personfields_rows_and_cols()" />
+ <al-expand name="demogfields_text" />
+
+ <p><b>This will irreversably merge the two selected records
+  into one record, merging access controls and logs. Be certain this is
+  what you want before clicking the Merge button.</b></p>
+ <al-exec expr="desc = casemerge.desc_edit()" />
+ <al-if expr="desc">
+  <table class="duperes">
+   <tr>
+    <th colspan="3">Edit actions</th>
+   </tr>
+   <al-for iter="s_i" expr="desc">
+    <al-exec expr="label, op, value, hilite = s_i.value()" />
+    <al-if expr="hilite"><tr class="warn"><al-else><tr></al-if>
+     <th class="op"><al-value expr="op" /></th>
+     <th class="label"><al-value expr="label" /></th>
+     <td><al-value expr="value" /></td>
+    </tr>
+   </al-for>
+  </table>
+ <al-else>
+  <div class="dupres">No edits are required</div>
+ </al-if>
+ <table width="100%">
+  <tr>
+   <td align="right">
+    <al-input name="merge" type="submit" class="butt" value="Merge" /><br>
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/mergecase_detail.py b/pages/mergecase_detail.py
new file mode 100644
index 0000000..385db2c
--- /dev/null
+++ b/pages/mergecase_detail.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import globals, casemerge, cases
+from pages import page_common, caseset_ops
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_merge(self, ctx, ignore):
+        cred = ctx.locals._credentials
+        try:
+            update_case, delete_case = ctx.locals.casemerge.merge(cred)
+        except casemerge.CaseHasChanged:
+            ctx.add_error('A conflicting edit has occurred - redo merge')
+            ctx.pop_page()
+        else:
+            globals.db.commit()
+            ctx.add_message('Merged System ID %s into %s' % 
+                            (delete_case.case_id, update_case.case_id))
+            caseset_ops.caseset_remove(ctx, delete_case.case_id)
+            # refresh case view
+            case = cases.edit_case(cred, update_case.case_id)
+            page_common.set_case(ctx, case)
+            ctx.pop_page('selmergecase')
+            if ctx.locals.case.can_merge_forms():
+                ctx.msg('warn', 'Now please check for duplicate forms...')
+                ctx.push_page('selmergeforms', ctx.locals.case)
+
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    pass
+
+def page_leave(ctx):
+    pass
+
+def page_display(ctx):
+    ctx.run_template('mergecase_detail.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/mergeform.html b/pages/mergeform.html
new file mode 100644
index 0000000..976e594
--- /dev/null
+++ b/pages/mergeform.html
@@ -0,0 +1,118 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+<al-include name="form_inputs.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge <al-value expr="formmerge.form_description" /> forms</al-setarg>
+ <al-exec expr="field_rows = formmerge_record.rows_and_cols('form')" />
+ <al-if expr="formmerge_record.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <table class="duperes">
+  <thead>
+   <tr class="top">
+    <td align="right">Source:</td>
+    <th>A</th>
+    <th>B</th>
+    <th>Clear</th>
+    <th>Edit</th>
+    <th></th>
+   </tr>
+  </thead>
+  <tfoot>
+   <tr>
+    <td colspan="6">
+     <table width="100%">
+      <tr>
+       <td align="right">
+        <al-input name="showmerge" type="submit" class="butt" value="Show Merge" />
+        <script>enterSubmit('appform', 'showmerge');</script>
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </tfoot>
+  <tbody>
+   <al-for iter="f_i" expr="formmerge.fields">
+    <al-exec expr="mc = f_i.value()" />
+    <al-if expr="mc.conflict"><tr class="warn"><al-else><tr></al-if>
+     <th class="label">
+      <label><al-value expr="mc.label" /></label>
+     </th>
+     <td>
+      <al-exec expr="value_a = mc.outtrans(formmerge.row_a)" />
+      <al-if expr="value_a">
+       <al-if expr="mc.source != 'e'">
+        <al-input type="radio" value="a" nameexpr="mc.source_field" />
+       </al-if>
+       <al-value expr="value_a" />
+      </al-if>
+     </td>
+     <td>
+      <al-exec expr="value_b = mc.outtrans(formmerge.row_b)" />
+      <al-if expr="value_b">
+       <al-if expr="mc.source != 'e'">
+        <al-input type="radio" value="b" nameexpr="mc.source_field" />
+       </al-if>
+       <al-value expr="value_b" />
+      </al-if>
+     </td>
+     <td align="center">
+      <al-if expr="mc.source != 'e'">
+       <al-input type="radio" value="d" nameexpr="mc.source_field" />
+      </al-if>
+     </td>
+     <td class="forminput">
+      <al-if expr="mc.source == 'e'">
+       <table class="syndrome-form" width="100%" cellpadding="0" cellspacing="0">
+        <al-exec expr="input = mc.get_input()" />
+        <al-if expr="mc.error">
+         <tbody class="errinp">
+          <tr><td colspan="2" class="error">
+           <al-value expr="mc.error" />
+          </td></tr>
+        <al-elif expr="input.required">
+         <tbody class="reqinp">
+        <al-else>
+         <tbody>
+        </al-if>
+         <al-value lookup="input_markup" expr="input.render" />
+        </tbody>
+       </table>
+      </al-if>
+     </td>
+     <td>
+      <al-if expr="mc.source == 'e'">
+       <al-input type="submit" nameexpr="mc.set_butt" 
+        value="No Edit" class="butt" />
+      <al-else>
+       <al-input type="submit" nameexpr="mc.set_butt" 
+        value="Edit" class="butt" />
+      </al-if>
+     </td>
+    </tr>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/mergeform.py b/pages/mergeform.py
new file mode 100644
index 0000000..60b2f7e
--- /dev/null
+++ b/pages/mergeform.py
@@ -0,0 +1,50 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_showmerge(self, ctx, ignore):
+        if not ctx.locals.formmerge.validate():
+            ctx.add_error('Fix field errors first')
+        else:
+            ctx.push_page('mergeform_detail')
+
+    def do_edit(self, ctx, index):
+        ctx.locals.formmerge.toggle_edit(int(index))
+
+pageops = PageOps()
+
+def page_enter(ctx, formmerge):
+    ctx.locals.formmerge = formmerge
+    ctx.locals.form_data = formmerge.form_data
+    ctx.add_session_vars('formmerge', 'form_data')
+
+def page_leave(ctx):
+    ctx.del_session_vars('formmerge', 'form_data')
+
+def page_display(ctx):
+    ctx.locals.form_disabled = False
+    ctx.run_template('mergeform.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
+
diff --git a/pages/mergeform_detail.html b/pages/mergeform_detail.html
new file mode 100644
index 0000000..a4ffd10
--- /dev/null
+++ b/pages/mergeform_detail.html
@@ -0,0 +1,57 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge <al-value expr="formmerge.form_description" /> forms - proposed actions</al-setarg>
+ <al-exec expr="field_rows = formmerge_record.rows_and_cols('form')" />
+ <al-if expr="formmerge_record.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <p><b>This will irreversably merge the selected forms into one record. Be
+ certain this is what you want before clicking the Merge button.</b></p>
+ <al-exec expr="desc = formmerge.desc_edit()" />
+ <al-if expr="desc">
+  <table class="duperes">
+   <tr>
+    <th colspan="3">Edit actions</th>
+   </tr>
+   <al-for iter="s_i" expr="desc">
+    <al-exec expr="label, op, value, hilite = s_i.value()" />
+    <al-if expr="hilite"><tr class="warn"><al-else><tr></al-if>
+     <th class="op"><al-value expr="op" /></th>
+     <th class="label"><al-value expr="label" /></th>
+     <td><al-value expr="value" /></td>
+    </tr>
+   </al-for>
+  </table>
+ <al-else>
+  <div class="dupres">No edits are required</div>
+ </al-if>
+ <table width="100%">
+  <tr>
+   <td align="right">
+    <al-input name="merge" type="submit" class="butt" value="Merge" />
+   </td>
+  </tr>
+ </table>
+</al-expand>
diff --git a/pages/mergeform_detail.py b/pages/mergeform_detail.py
new file mode 100644
index 0000000..a0c1337
--- /dev/null
+++ b/pages/mergeform_detail.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, formmerge
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_merge(self, ctx, ignore):
+        try:
+            ctx.locals.formmerge.merge(ctx.locals._credentials)
+        except formmerge.FormHasChanged:
+            ctx.add_error('A conflicting edit has occurred - redo merge')
+            ctx.pop_page()
+        else:
+            globals.db.commit()
+            ctx.add_message('Merged forms')
+            # refresh form view
+            ctx.locals.formmerge_record.forms.load()
+            ctx.pop_page()  # To mergeform
+            ctx.pop_page()  # To selmergeforms
+            if not ctx.locals.formmerge_record.can_merge_forms():
+                ctx.pop_page()  # To case/casecontact
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    pass
+
+def page_leave(ctx):
+    pass
+
+def page_display(ctx):
+    ctx.run_template('mergeform_detail.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/mergeperson.html b/pages/mergeperson.html
new file mode 100644
index 0000000..be34e5b
--- /dev/null
+++ b/pages/mergeperson.html
@@ -0,0 +1,136 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge person record</al-setarg>
+ <table class="duperes">
+  <thead>
+   <al-if expr="personmerge.status == persondupe.STATUS_EXCLUDED">
+    <tr>
+     <td colspan="5">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr class="warn">
+        <td width="100%">Not a duplicate<al-if expr="personmerge.exclude_reason">
+          : <al-value expr="personmerge.exclude_reason" />
+         </al-if>
+        </td>
+        <td>
+          <al-input type="submit" name="include" class="bigbutt" 
+           value="Remove exclusion" />
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   <al-else>
+    <tr>
+     <td colspan="5">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td nowrap>Exclusion reason:</td>
+        <td width="100%">
+         <al-input style="width: 100%;" name="personmerge.exclude_reason" />
+        </td>
+        <td>
+         <al-input type="submit" name="exclude" class="bigbutt" 
+          value="Not a duplicate" />
+        </td>
+       </tr>
+      </table>
+     </td>
+    </tr>
+   </al-if>
+   <tr class="top">
+    <td align="right">Source:</td>
+    <th>A</th>
+    <th>B</th>
+    <th>Edit</th>
+    <th>Clear</th>
+   </tr>
+   <tr class="top">
+    <td align="right">System IDs:</td>
+    <al-for vars="cases" expr="personmerge.desc_person_cases()">
+     <th>
+      <table class="smaller">
+       <al-for vars="casedesc" expr="cases">
+        <al-tr classexpr="casedesc.style">
+         <td><al-value expr="casedesc.case_id" /></td>
+         <td><al-value expr="casedesc.syndrome" /></td>
+        </al-tr>
+       </al-for>
+      </table>
+     </th>
+    </al-for>
+    <th>
+     <al-input name="cases" type="submit" class="bigbutt" value="Case" />
+    </th>
+    <th></th>
+   </tr>
+  </thead>
+  <tfoot>
+   <tr>
+    <td colspan="5">
+     <table width="100%">
+      <tr>
+       <td align="right">
+        <al-input name="showmerge" type="submit" class="butt" value="Show Merge" />
+        <script>enterSubmit('appform', 'showmerge');</script>
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </tfoot>
+  <tbody>
+   <al-for iter="f_i" expr="personmerge.fields">
+    <al-exec expr="mc = f_i.value()" />
+    <al-if expr="mc.conflict"><tr class="warn"><al-else><tr></al-if>
+     <th class="label">
+      <al-label forexpr="mc.field.field">
+       <al-value expr="mc.label" />
+      </al-label>
+     </th>
+     <td>
+      <al-exec expr="value_a = mc.outtrans(personmerge.person_a)" />
+      <al-if expr="mc.show_a">
+       <al-input type="radio" value="a" nameexpr="mc.source_field" />
+       <al-value expr="value_a" />
+      </al-if>
+     </td>
+     <td>
+      <al-exec expr="value_b = mc.outtrans(personmerge.person_b)" />
+      <al-if expr="mc.show_b">
+       <al-input type="radio" value="b" nameexpr="mc.source_field" />
+       <al-value expr="value_b" />
+      </al-if>
+     </td>
+     <td class="user">
+      <al-exec expr="field = mc.field" />
+      <al-value lookup="field_render" expr="field.render" />
+     </td>
+     <td align="center">
+      <al-input type="radio" value="d" nameexpr="mc.source_field" /></td>
+    </tr>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/mergeperson.py b/pages/mergeperson.py
new file mode 100644
index 0000000..e37c608
--- /dev/null
+++ b/pages/mergeperson.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, personmerge, persondupe, cases
+from pages import page_common, caseset_ops
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_showmerge(self, ctx, ignore):
+        ctx.locals.personmerge.normalise()
+        ctx.push_page('mergeperson_detail')
+
+    def do_exclude(self, ctx, ignore):
+        ctx.locals.personmerge.exclude()
+        ctx.pop_page()
+
+    def do_include(self, ctx, ignore):
+        ctx.locals.personmerge.include()
+
+    def do_cases(self, ctx, ignore):
+        caseset_ops.make_caseset(ctx, ctx.locals.personmerge.cases(),
+                            'Cases associated with a prospective person merge')
+
+
+pageops = PageOps()
+
+def page_enter(ctx, personmerge):
+    ctx.locals.personmerge = personmerge
+    ctx.add_session_vars('personmerge')
+    if config.debug:
+        desc = persondupe.explain_dupe(ctx.locals._credentials.prefs,
+                                       personmerge.person_a,
+                                       personmerge.person_b)
+        ctx.add_message('Similarity: %s' % desc)
+
+def page_leave(ctx):
+    try:
+        likely = ctx.locals.likely
+        personmerge = ctx.locals.personmerge
+    except AttributeError:
+        pass
+    else:
+        likely.set_cur_exclude(personmerge.status, personmerge.exclude_reason)
+    ctx.del_session_vars('personmerge')
+    ctx.locals.caseset = None
+
+def page_display(ctx):
+    ctx.run_template('mergeperson.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/mergeperson_detail.html b/pages/mergeperson_detail.html
new file mode 100644
index 0000000..5ade688
--- /dev/null
+++ b/pages/mergeperson_detail.html
@@ -0,0 +1,54 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge person record - proposed actions</al-setarg>
+ <p><b>This will irreversably merge the two selected person records
+ into one record, and reassociate all contacts with the merged record. Be
+ certain this is what you want before clicking the Merge button.</b></p>
+ <p>This merge effects System IDs: <al-value expr="personmerge.desc_cases()" /> 
+  (deleted records shown in brackets).</p>
+ <al-exec expr="desc = personmerge.desc_edit()" />
+ <al-if expr="desc">
+  <table class="duperes">
+   <tr>
+    <th colspan="3">Edit actions</th>
+   </tr>
+   <al-for iter="s_i" expr="desc">
+    <al-exec expr="label, op, value, hilite = s_i.value()" />
+    <al-if expr="hilite"><tr class="warn"><al-else><tr></al-if>
+     <th class="op"><al-value expr="op" /></th>
+     <th class="label"><al-value expr="label" /></th>
+     <td><al-value expr="value" /></td>
+    </tr>
+   </al-for>
+  </table>
+ <al-else>
+  <div class="dupres">No edits are required</div>
+ </al-if>
+ <table width="100%">
+  <tr>
+   <td align="right">
+    <al-input name="merge" type="submit" class="butt" value="Merge" /><br>
+   </td>
+  </tr>
+ </table>
+</al-expand>
+
diff --git a/pages/mergeperson_detail.py b/pages/mergeperson_detail.py
new file mode 100644
index 0000000..5cb75f5
--- /dev/null
+++ b/pages/mergeperson_detail.py
@@ -0,0 +1,65 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import utils
+from casemgr import personmerge, globals, casemerge
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_merge(self, ctx, ignore):
+        cred = ctx.locals._credentials
+        try:
+            person, ids = ctx.locals.personmerge.merge(cred)
+        except personmerge.PersonHasChanged:
+            ctx.add_error('A conflicting edit has occurred - redo merge')
+            ctx.pop_page()
+            return
+        globals.db.commit()
+        ctx.add_message('Merged persons associated with System IDs %s'%ids)
+        if 'dupepersons' in ctx.locals.__pages__:
+            ctx.pop_page('dupepersons')
+            #ctx.locals.likely.new_search()
+        else:
+            ctx.pop_page()  # mergeperson
+            ctx.pop_page()
+        try:
+            selmerge = casemerge.by_person(ctx.locals._credentials, person)
+        except casemerge.NoOtherRecords:
+            pass
+        else:
+            ctx.add_message('Now please check for duplicate case/contact records...')
+            ctx.push_page('selmergecase', selmerge)
+
+
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    pass
+
+def page_leave(ctx):
+    pass
+
+def page_display(ctx):
+    ctx.run_template('mergeperson_detail.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/nocookies.html b/pages/nocookies.html
new file mode 100644
index 0000000..dd49eee
--- /dev/null
+++ b/pages/nocookies.html
@@ -0,0 +1,31 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="error_layout.html" />
+<al-expand name="error_layout">
+ <al-setarg name="title">Session Expired</al-setarg>
+ <p>Your session credentials are invalid - this may be because your session
+  ID expired due to <al-value expr="session_limit()" /> of inactivity,
+  or because you have been logged out.</p>
+ <p>In either case, you will need to log back in if you wish to
+ continue to
+  use <al-value expr="apptitle" />.</p>
+ <p><input type="submit" name="okay" value="Okay"></p>
+</al-expand>
diff --git a/pages/notetask.html b/pages/notetask.html
new file mode 100644
index 0000000..80ecde0
--- /dev/null
+++ b/pages/notetask.html
@@ -0,0 +1,28 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="taskbanner.html" />
+<al-include name="taskedit.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Note Task</al-setarg>
+ <al-expand name="taskbanner" />
+ <al-expand name="taskedit" />
+</al-expand>
diff --git a/pages/notetask.py b/pages/notetask.py
new file mode 100644
index 0000000..68f7bf1
--- /dev/null
+++ b/pages/notetask.py
@@ -0,0 +1,38 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, tasks
+from pages import page_common, taskedit
+import config
+
+pageops = taskedit.PageOps()
+
+def page_enter(ctx, inplace=False):
+    edittask = tasks.EditTask(globals.db, ctx.locals._credentials, 
+                              ctx.locals.task, inplace=inplace)
+    taskedit.page_enter(ctx, None, edittask)
+
+def page_leave(ctx):
+    taskedit.page_leave(ctx)
+
+def page_display(ctx):
+    taskedit.page_display(ctx)
+    ctx.run_template('notetask.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/page_common.py b/pages/page_common.py
new file mode 100644
index 0000000..71fcc3f
--- /dev/null
+++ b/pages/page_common.py
@@ -0,0 +1,367 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+import config
+from cocklebur import pageops
+from cocklebur.pageops import download, csv_download, send_download, \
+                              Confirm, ConfirmSave, \
+                              ConfirmDelete, ConfirmUndelete, ConfirmRevert
+from cocklebur import dbobj, form_ui, checkdigit
+from casemgr import globals, cases, tasks, taskdesc, credentials, messages
+from casemgr.tabs import Tabs
+
+
+class PageError(globals.Error): pass
+
+
+class ConfirmUnlock(Confirm):
+    mode = 'unlock'
+    title = 'Take ownership'
+    message = 'This record is externally sourced. Taking ownership will stop any external updates occurring and allow local editing. This can not be reversed. Are you sure you wish to do this?'
+    buttons = [
+        ('continue', 'No'),
+        ('confirm', 'Yes'),
+    ]
+
+
+class PageOpsBase(pageops.PageOpsBase):
+    debug = config.debug
+    home_page = 'main'
+    app_errors = (
+        globals.Error,
+        dbobj.DatabaseError, 
+        form_ui.FormError,
+        credentials.CredentialError,
+    )
+
+    def do_admin(self, ctx, ignore):
+        self.check_unsaved_or_confirmed(ctx)
+        ctx.pop_page('admin')
+
+    def do_logout(self, ctx, ignore):
+        unlock_task(ctx)
+        ctx.locals._credentials.commit_prefs(globals.db, immediate=True)
+        pageops.PageOpsBase.do_logout(self, ctx, ignore)
+
+    def do_menubardrop(self, ctx, action):
+        ctx.locals.menubardrop = None
+        args = action.split(':')
+        action = args.pop(0)
+        if not args:
+            args = None,
+        if hasattr(self, 'do_' + action):
+            self.dispatch(ctx, action, args)
+        else:
+            return pageops.ContinueDispatch
+
+    def do_page(self, ctx, *args):
+        ctx.locals.paged_search.do(*args)
+
+    def page_process(self, ctx):
+        client_report(ctx)
+        ctx.locals._credentials.commit_prefs(globals.db)
+        try:
+            paged_search = getattr(ctx.locals, 'paged_search', None)
+            if paged_search is not None:
+                paged_search.page_jump()
+            return pageops.PageOpsBase.page_process(self, ctx)
+        except messages.Messages, msgs:
+            ctx.add_messages(msgs)
+            globals.db.rollback()
+        except self.app_errors, e:
+            # ctx.set_page() & ctx.push_page() indirectly invoke the page
+            # page_enter() method. If this throws an exception that we catch, 
+            # the page_display() method may be called without appropriate
+            # initialisation, resulting in obscure failures.
+            tb = sys.exc_info()[2].tb_next
+            while tb:
+                if tb.tb_frame.f_code.co_name == 'page_enter':
+                    ctx.pop_page()
+                    break
+                tb = tb.tb_next
+            globals.db.rollback()
+            ctx.locals.confirm = None
+            ctx.add_error(e)
+
+
+page_process = PageOpsBase().page_process       # Legacy
+
+
+class PageOpsLeaf(PageOpsBase):
+    # For pages of this type (sub pages of a page with "unsaved changes"), we
+    # pass the logout/home/admin/back process up to the parent page. The
+    # mechanism is somewhat of a hack, but it's the best we can do for now.
+
+    def page_process_parent(self, ctx, *ignore):
+        try:
+            self.check_unsaved_or_confirmed(ctx)
+        except pageops.Confirm:
+            # Page model isn't sophisticated enough to support this:
+            raise NotImplementedError
+        ctx.pop_page()
+        ctx.page.page_process(ctx)
+
+    do_logout = page_process_parent
+
+    def do_home(self, ctx, ignore):
+        if ctx.locals.__pages__[-1] == self.home_page:
+            PageOpsBase.action_home(self, ctx)
+        else:
+            self.page_process_parent(ctx)
+
+    def do_admin(self, ctx, ignore):
+        if ctx.locals.__pages__[-1] == 'admin':
+            PageOpsBase.do_admin(self, ctx, ignore)
+        else:
+            self.page_process_parent(ctx)
+
+
+def client_report(ctx):
+    """
+    Pull some hidden fields out of the client request, and log their
+    contents (along with some other useful bits like the instance name,
+    authenticated user, and client IP address). These can subsequently be
+    analysed to identify network and application bottlenecks.
+    """
+    fields = dict(
+        client    = getattr(ctx.locals, 'response_time', None),
+        forwarded = ctx.request.get_forwarded_addr(),
+        inst      = config.appname,
+        ip        = ctx.request.get_remote_addr(),
+        page      = getattr(ctx.locals, 'last_page', None),
+        server    = getattr(ctx.locals, 'server_time', None),
+        start     = getattr(ctx.locals, 'start_time', None),
+        unit      = '',
+        user      = '',
+    )
+    if ctx.locals._credentials:
+        fields['user'] = str(ctx.locals._credentials.user.user_id)
+        fields['unit'] = str(ctx.locals._credentials.unit.unit_id)
+    pairs = []
+    for key, value in fields.iteritems():
+        if value:
+            pairs.append('%s=%s' % (key, str(value)[:80].replace(' ', '_')))
+    if fields['page']:
+        print >> sys.stderr, 'client report: %s' % (' '.join(pairs))
+
+
+def merge_rights(ptset):
+    rights = credentials.Rights()
+    for row in ptset:
+        if row.rights:
+            rights.add(row.rights)
+    return rights
+
+
+def idle(ctx):
+    ctx.locals.case = None
+    ctx.locals.caseset = None
+    unlock_task(ctx)
+
+
+class MenuBarItem:
+    drop = None
+
+    def __init__(self, name, label, style='butt'):
+        self.name = name
+        self.label = label
+        self.style = style
+
+    def linkjs(self):
+        return "javascript:linksubmit('appform','%s');" % self.name.lower()
+
+    def add_drop(self, name, label, style=None):
+        if self.drop is None:
+            self.drop = []
+        self.drop.append((name, label))
+
+    def add_droprule(self):
+        if self.drop is not None and self.drop[-1][0]:
+            self.drop.append(('-', ''))
+
+    def optionexpr(self):
+        return [('', self.label)] + self.drop
+
+
+class MenuBar:
+
+    def __init__(self):
+        self.left = []
+        self.middle = []
+        self.right = []
+        self.button_width = 4
+
+    def _add(self, which, *args, **kwargs):
+        item = MenuBarItem(*args, **kwargs)
+        which.append(item)
+        width = len(item.label)
+        if width > self.button_width:
+            self.button_width = width
+        return item
+
+    def table_row(self):
+        yield 'mbl', self.left
+        yield 'mbm', self.middle
+        yield 'mbr', self.right
+
+    def add_left(self, *args, **kwargs):
+        return self._add(self.left, *args, **kwargs)
+
+    def add_middle(self, *args, **kwargs):
+        return self._add(self.middle, *args, **kwargs)
+
+    def add_right(self, *args, **kwargs):
+        return self._add(self.right, *args, **kwargs)
+
+    def done(self):
+        self.style = 'width: %.1fem;' % (self.button_width * 0.75)
+
+
+def bannertabs(ctx):
+    tabs = MenuBar()
+    page = ctx.locals.__page__
+    pages = ctx.locals.__pages__
+    cred = ctx.locals._credentials
+    if page == 'admin_form_edit_question' or ctx.locals.confirm:
+        # The question editor can't safely say if there are unsaved changes or
+        # not, so we disallow home & logout within it.
+        return tabs
+    if page not in ('main',):
+        tabs.add_left('back', 'Back')
+    if 'ADMIN' in cred.rights and 'admin' in pages:
+        tabs.add_left('admin', 'Admin')
+    if page == 'main':
+        tabs.add_middle('query_tasks', 'Tasks')
+        tabs.add_middle('tools', 'Tools')
+        if 'ADMIN' in cred.rights:
+            tabs.add_middle('admin', 'Admin')
+    else:
+        if 'TASKINIT' in cred.rights and page in ('tasks', 'casetasks'):
+            tabs.add_middle('note', 'Note')
+        if 'main' in pages:
+            tabs.add_left('home', 'Home')
+    if page in ('case', 'caseform'):
+        tabs.add_right('print', 'Print')
+    tabs.add_right('logout', 'Logout')
+    return tabs
+
+
+# Task related stuff that changes application state and consequently doesn't
+# belong in the tasks module.
+def unlock_task(ctx):
+    # During logout, task can be deleted from ctx
+    task = getattr(ctx.locals, 'task', None)
+    if task is not None:
+        ctx.locals.task.unlock(globals.db)
+        globals.db.commit()
+        ctx.locals.task = None
+
+
+def task_update(ctx, task_desc):
+    if ctx.locals.task is not None:
+        ctx.locals.task.entity_update(globals.db, **task_desc)
+
+
+def is_cur_task(ctx, task_desc):
+    if ctx.locals.task is not None:
+        return ctx.locals.task.same_entity(**task_desc)
+    return False
+
+
+def unlock_desc_task(ctx, task_desc):
+    if ctx.locals.task is not None:
+        if ctx.locals.task.same_entity(**task_desc):
+            unlock_task(ctx)
+            return True
+    return False
+
+
+def set_case(ctx, case):
+    # Update caseset
+    if ctx.locals.caseset is not None:
+        if ctx.locals.caseset.cur() != case.case_row.case_id:
+            try:
+                ctx.locals.caseset.seek_case(case.case_row.case_id)
+            except ValueError:
+                ctx.locals.caseset = None
+    # Update tabs
+    select = None
+    if ctx.locals.case is not None and hasattr(ctx.locals.case, 'tabs'):
+        select = ctx.locals.case.tabs.selected
+    case.init_tabs(select)
+    ctx.locals.case = case
+
+
+def edit_case(ctx, case, push=False):
+    set_case(ctx, case)
+    if push:
+        ctx.push_page('case')
+    else:
+        ctx.set_page('case')
+
+
+def precreate_case(ctx, case):
+    if config.immediate_create:
+        try:
+            log = case.db_desc()
+            case.update()
+            case.user_log(log)
+            globals.db.commit()
+            ctx.add_message('Case created')
+        except dbobj.DatabaseError, e:
+            globals.db.rollback()
+            ctx.add_error(e)
+
+
+def new_case(ctx, syndrome_id, **kw):
+    case = cases.new_case(ctx.locals._credentials, syndrome_id, **kw)
+    precreate_case(ctx, case)
+    return case
+
+
+def go_id(ctx, id):
+    """
+    Given a check-digit protected entity ID, load the appropriate object
+    and push the appropriate edit page.
+    """
+    if not id:
+        return
+    if id.isdigit():
+        id = int(id)
+        case = cases.edit_case(ctx.locals._credentials, id)
+        ctx.locals.caseset = None
+        edit_case(ctx, case, push=True)
+        return
+    else:
+        id_type = id[0].upper()
+        id_okay, id = checkdigit.check_checkdigit(id[1:])
+        if id_okay and id_type == 'F':
+            case, ef = cases.edit_form(ctx.locals._credentials, id)
+            ctx.locals.caseset = None
+            edit_case(ctx, case, push=True)
+            ctx.push_page('caseform', ef)
+            return
+    raise PageError('Invalid ID')
+
+
+def alternate(idx):
+    if idx & 1:
+        return 'darker'
+    else:
+        return 'lighter'
diff --git a/pages/page_layout.html b/pages/page_layout.html
new file mode 100644
index 0000000..8f33bf2
--- /dev/null
+++ b/pages/page_layout.html
@@ -0,0 +1,420 @@
+<al-require version="2">
+<al-macro name="page_common">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
+   "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+
+ <html>
+  <head>
+   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+   <meta http-equiv="Content-Script-Type" content="text/javascript">
+   <meta name="robots" content="noindex,nofollow">
+   <al-if expr="session_timeout">
+    <meta http-equiv="Refresh" content="<al-value expr='session_timeout + 1' />">
+   </al-if>
+   <al-link rel="shortcut icon" hrefexpr="appath('images','favicon.ico')" type="image/x-icon">
+   <script type="text/javascript">
+    var _is_complete=false;
+    var set_complete = function () {
+     _is_complete=true;
+     var wait=document.getElementById('wait');
+     if (wait) wait.style.display='none';
+    }
+    var is_complete = function () {
+     if (_is_complete) return true;
+     var wait=document.getElementById('wait');
+     if (wait) wait.style.display='block';
+     return false;
+    }
+   </script>
+   <al-usearg name="head" />
+   <al-setdefault name="extrahead" />
+   <al-usearg name="extrahead" />
+   <al-if expr="has_js == 'yes' and _credentials and _credentials.prefs.get('nobble_back_button')">
+    <al-script srcexpr="appath('nobbleback.js')" type="text/javascript"></al-script>
+   </al-if>
+   <al-setdefault name="extrastyle" />
+   <style type="text/css" media="all">
+    <!-- <al-usearg name="style" /> <al-usearg name="extrastyle" /> -->
+   </style>
+   <title><al-usearg name="title" /> - <al-value expr="apptitle" /></title>
+  </head>
+  <body onload="set_complete();">
+   <al-if expr="has_js == 'yes' and _credentials and _credentials.prefs.get('nobble_back_button')">
+    <iframe style="border: 0px; width: 1px; height: 1px; position: absolute; bottom: 0px; right: 0px; visibility: visible;" name="bif" id="bif" src="<al-value expr="appath('backiframe.html')">"></iframe>
+   </al-if>
+   <div id="wait" style="color:red;padding:.5ex;background:#fee;border:solid 2px red;position:absolute;display:none;font-size:12pt;">Page is loading, please wait...</div>
+   <al-usearg>
+  </body>
+ </html>
+</al-macro>
+
+<al-macro name="req-sorttable">
+  <al-script srcexpr="appath('sorttable.js')" type="text/javascript"></al-script>
+</al-macro>
+
+<al-macro name="print_layout">
+ <al-expand name="page_common">
+  <al-setarg name="head">
+   <al-if expr="has_js == 'yes'">
+    <al-script srcexpr="appath('helpers.js')" type="text/javascript"></al-script>
+   </al-if>
+  </al-setarg>
+  <al-setarg name="style">
+   <al-comment>
+    <!-- we want these inline, so that the HTML is self-contained -->
+   </al-comment>
+   <al-include expr="html_target + '/printforms.css'" />
+   <al-include expr="html_target + '/wiki.css'" />
+  </al-setarg>
+  <al-form name="appform" method="post" autocomplete="off">
+   <al-expand name="render_msgs" />
+   <al-expand name="print_buttons" />
+   <al-usearg />
+   <al-expand name="print_buttons" />
+   <al-expand name="metric_fields" />
+  </al-form>
+ </al-expand>
+</al-macro>
+
+
+<al-macro name="help_button">
+      <al-if expr="__page__.startswith('admin')">
+       <al-exec expr="help_html = 'admin_help.html'" />
+      <al-else>
+       <al-exec expr="help_html = 'help.html'" />
+      </al-if>
+      <al-if expr="has_js == 'yes'">
+       <al-button onclickexpr="'pophelp(%r, %r);' % (appath('help', help_html), __page__)" id="help"><al-img expr="appath('images','help-g.png')" border="0" alt="Help" /></al-button>
+      <al-else>
+       <al-a expr="appath('help', help_html + '#' +  __page__)" target="_blank" id="help"><al-img expr="appath('images','help-g.png')" border="0" alt="Help" /></al-a>
+      </al-if>
+</al-macro>
+
+<al-macro name="wikihelp">
+ <al-if expr="has_js == 'yes'">
+  <al-button class="wikihelp" onclickexpr="'pophelp(%r, %r);' % (appath('help', 'wiki_help.html'), '')">W</al-button>
+ <al-else>
+  <al-a expr="appath('help', 'wiki_help.html')" class="wikihelp" target="_blank">W</al-a>
+ </al-if>
+</al-macro>
+
+<al-macro name="render_msgs">
+   <al-for vars="lvl, msg" expr="get_messages()">
+    <al-div classexpr="lvl + '-msg'"><al-value expr="msg" /></al-div>
+   </al-for>
+</al-macro>
+
+<al-macro name="metric_fields">
+   <al-input type="hidden" name="response_time" value="" />
+   <al-input type="hidden" name="start_time" expr="int(request_start)" />
+   <al-input type="hidden" name="server_time" 
+    expr="'%.3f' % request_elapsed()" />
+   <al-input type="hidden" name="last_page" expr="__page__" />
+</al-macro>
+
+<al-macro name="debug_box">
+    <al-if expr="debug">
+     <div class="debug">
+      <b>Debug:</b>
+      <b>Page module:</b> <al-value expr="__page__" />,
+      <b>Has JS:</b> <al-input type="checkbox" name="has_js" value="yes" onclick="submit();" />,
+      <b>Version:</b> <al-value expr="__version__" />,
+      <b>Py:</b> <al-value expr="__pyver__" />,
+      <b>SVN:</b> <al-value expr="__svnrev__" />,
+      <b>deploy:</b> <al-value expr="deploy_mode" />,
+      <b>Rights:</b> <al-value expr="_credentials.rights" />,
+      <b>Elapsed:</b> <al-value expr="'%.3fs' % request_elapsed()" />, 
+      <b>Page stack:</b> <al-value expr="'>'.join(__pages__)" />
+     </div>
+    </al-if>
+</al-macro>
+
+<al-macro name="banner_box">
+   <al-setdefault name="banner_extra" />
+   <table class="pagebanner">
+    <tr>
+     <td class="logo"><al-img expr="appath('images','netepi.png')" alt="NetEpi logo"></td>
+     <td class="title">
+      <div class="apptitle"><al-value expr="config.apptitle" /></div>
+      <div class="subbanner"><al-value expr="config.subbanner" /></div>
+     </td>
+     <al-usearg name="banner_extra" />
+     <td align="center">
+      <al-expand name="help_button" />
+     </td>
+    </tr>
+   </table>
+   <table class="menubar">
+    <tr>
+     <al-for vars="cell_style, items" expr="page_common.bannertabs(__ctx__).table_row()">
+      <al-td classexpr="cell_style">
+       <al-for vars="item" expr="items">
+        <al-input type="submit" nameexpr="item.name" idexpr="item.name" 
+         expr="item.label" class="sublink" />
+       </al-for>
+      </al-td>
+     </al-for>
+    </tr>
+   </table>
+</al-macro>
+
+<al-macro name="page_layout">
+ <al-expand name="page_common">
+  <al-setarg name="head">
+   <al-if expr="has_js == 'yes'">
+    <al-if expr="_credentials and _credentials.prefs.get('jscalendar')">
+     <al-script srcexpr="appath('calendar.js')" type="text/javascript"></al-script>
+     <al-script srcexpr="appath('lang/calendar-en.js')" type="text/javascript"></al-script>
+    </al-if>
+    <al-script srcexpr="appath('helpers.js')" type="text/javascript"></al-script>
+   </al-if>
+  </al-setarg>
+  <al-setarg name="style">
+    @import "/<al-value expr='appname' />/style.css";
+    @import "/<al-value expr='appname' />/wiki.css";
+    @import "/<al-value expr='appname' />/calendar.css";
+    <al-if expr="_credentials">
+     body { font-size: <al-value expr="_credentials.prefs.get('font_size')">;}
+    </al-if>
+  </al-setarg>
+  <al-form name="appform" action="app.py#cur" method="post" autocomplete="off"  onsubmit="return is_complete();">
+   <al-usearg />
+   <al-expand name="metric_fields" />
+  </al-form>
+ </al-expand>
+</al-macro>
+
+<al-macro name="page_layout_banner">
+ <al-setdefault name="subtitle" />
+ <al-expand name="page_layout">
+  <al-setarg name="title"><al-usearg name="title" /></al-setarg>
+   <al-expand name="banner_box" />
+   <al-expand name="render_msgs" />
+   <div class="pagetitle">
+    <al-usearg name="title" />
+   </div>
+   <div class="content">
+    <al-usearg />
+   </div>
+   <al-expand name="debug_box" />
+ </al-expand>
+</al-macro>
+
+<al-macro name="page_layout_banner_scroll">
+ <al-setdefault name="subtitle" />
+ <al-expand name="page_layout">
+  <al-setarg name="title"><al-usearg name="title" /></al-setarg>
+   <al-expand name="banner_box" />
+   <al-expand name="render_msgs" />
+   <div class="pagetitle">
+    <al-usearg name="title" />
+   </div>
+   <div class="scroll-content">
+    <al-usearg />
+   </div>
+   <al-expand name="debug_box" />
+ </al-expand>
+</al-macro>
+
+<al-macro name="page_layout_admin">
+ <al-expand name="page_layout_banner">
+  <al-setarg name="title">Admin: <al-usearg name="title" /></al-setarg>
+  <al-usearg />
+ </al-expand>
+</al-macro>
+
+<al-macro name="confirm">
+  <a name="cur" />
+  <div class="centbox">
+   <al-if expr="confirm.title">
+    <h1><al-value expr="confirm.title" /></h1>
+   </al-if>
+   <al-if expr="confirm.message">
+    <p><al-value expr="confirm.message" /></p>
+   </al-if>
+   <al-if expr="confirm.reason_prompt">
+    <p><al-value expr="confirm.reason_prompt" />
+       <al-input name="confirm.reason" size="50" /></p>
+   </al-if>
+   <al-for iter="cb_i" expr="confirm.buttons">
+    <al-input type="submit" nameexpr="'confirm:%s' % cb_i.value()[0]" 
+     class="bigbutt" expr="cb_i.value()[1]" />
+   </al-for>
+  </div>
+</al-macro>
+
+<al-macro name="confirm_or_error">
+ <al-if expr="confirm">
+  <al-expand name="confirm" />
+ <al-else>
+  <al-usearg />
+ </al-if>
+</al-macro>
+
+<al-lookup name="boolean">
+ <al-item expr="'True'">Yes</al-item>
+ <al-item expr="''">No</al-item>
+</al-lookup>
+
+<al-macro name="print_buttons">
+   <al-setdefault name="extrabuttons" />
+   <div class="noprint">
+    <al-input class="noprint butt" type="submit" name="back" value="<<" />
+    <al-usearg name="extrabuttons" />
+    <input class="noprint butt" type="button" name="print" value="Print" 
+     onclick="window.print()" />
+   </div>
+</al-macro>
+
+<al-macro name="confidential">
+   <div class="confidential">
+    Confidental personal health data for authorised use only - please
+    store or dispose of securely.<br>
+    Printed by <al-value expr="_credentials.user.fullname" /> 
+    (<al-value expr="_credentials.user.username" />) 
+    on <al-value expr="datetime.now()" />.
+   </div>
+</al-macro>
+
+<al-macro name="caseset">
+     <al-if expr="caseset is not None">
+      <table class="menubar">
+       <tr>
+        <td class="mbl" width="33%">
+         <al-input class="mbsb" type="submit" name="caseset_close" value="X" />
+         <al-input class="mbsb" type="submit" name="caseset_remove" value="-" />
+        </td>
+        <td class="mbm" width="34%">
+         <table class="mbp">
+          <tr>
+           <td>
+            <al-if expr="caseset.inrange(-20)">
+             <al-input class="mbsb" type="submit" name="caseset_seek:-20" value="<<" />
+            </al-if>
+            <al-if expr="caseset.inrange(-1)">
+             <al-input class="mbsb" type="submit" name="caseset_seek:-1" value="<" />
+            </al-if>
+           </td>
+           <td class="mbpt">
+            <al-value expr="caseset.info()" />
+           </td>
+           <td>
+            <al-if expr="caseset.inrange(1)">
+             <al-input class="mbsb" type="submit" name="caseset_seek:1" value=">" />
+            </al-if>
+            <al-if expr="caseset.inrange(20)">
+             <al-input class="mbsb" type="submit" name="caseset_seek:20" value=">>" />
+            </al-if>
+           </td>
+          </tr>
+         </table>
+        </td>
+        <td class="mbr" width="33%">
+         <al-if expr="casesets.new_name">
+          Rename:
+          <al-input type="text" name="casesets.new_name" />
+          <al-input type="submit" name="casesets_action:rename_okay" 
+           class="smallbutt" value="Okay" />
+          <al-input type="submit" name="casesets_action:rename_cancel" 
+           class="smallbutt" value="Cancel" />
+         <al-else>
+          <al-select name="casesets_action" onchange="submit();"
+           optionexpr="casesets.actionoptions(caseset)" />
+         </al-if>
+        </td>
+       </tr>
+      </table>
+     </al-if>
+</al-macro>
+
+<al-macro name="menubar">
+     <table class="menubar">
+      <tr>
+       <al-for vars="cell_style, items" expr="menubar.table_row()">
+        <al-td classexpr="cell_style">
+         <al-for iter="item_i" expr="items">
+          <al-exec expr="item = item_i.value()" />
+          <al-if expr="item.drop">
+           <al-select nameexpr="item.name" onchange="submit();" 
+            optionexpr="item.optionexpr()" />
+          <al-else>
+           <al-input type="submit" nameexpr="item.name" idexpr="item.name" 
+            expr="item.label" classexpr="item.style" 
+            styleexpr="menubar.style" />
+          </al-if>
+         </al-for>
+        </al-td>
+       </al-for>
+      </tr>
+     </table>
+</al-macro>
+
+<al-macro name="page_select">
+   <al-if expr="paged_search.has_pages()">
+    <table border="0" cellpadding="0" cellspacing="0" class="page-select">
+     <tr>
+      <al-if expr="paged_search.has_prev()">
+       <td align="left">
+        <al-input type="submit" class="smallbutt" 
+         name="page:prev" value="PREV" />
+       </td>
+      </al-if>
+      <td align="center" width="100%">
+       <al-select name="paged_search.go_page" list="list" onchange="submit();"
+        optionexpr="paged_search.page_list()" 
+        expr="'Page %d' % paged_search.cur_page()" />
+       <al-input type="submit" class="smallbutt" name="page:goto" value="Go" />
+      </td>
+      <al-if expr="paged_search.has_next()">
+       <td align="right">
+        <al-input type="submit" class="smallbutt" 
+         name="page:next" value="NEXT" />
+       </td>
+      </al-if>
+     </tr>
+    </table>
+   </al-if>
+</al-macro>
+
+<al-macro name="sort_header">
+ <al-for iter="h_i" expr="paged_search.headers">
+  <al-exec expr="col, label = h_i.value()" />
+  <th nowrap>
+   <al-if expr="col">
+    <al-if expr="col != paged_search.order_by">
+     <al-input type="image" srcexpr="appath('images','sortdn.png')"
+      nameexpr="'page:orderby:%s' % col" />
+    <al-else>
+     <al-img expr="appath('images','sortdnsel.png')" />
+    </al-if>
+    <al-if expr="col  + ' DESC'!= paged_search.order_by">
+     <al-input type="image" srcexpr="appath('images','sortup.png')"
+      nameexpr="'page:orderby:%s_desc' % col" />
+    <al-else>
+     <al-img expr="appath('images','sortupsel.png')" />
+    </al-if>
+   </al-if>
+    
+   <al-value expr="label">
+  </th>
+ </al-for>
+</al-macro>
diff --git a/pages/prefsedit.html b/pages/prefsedit.html
new file mode 100644
index 0000000..9f2cdde
--- /dev/null
+++ b/pages/prefsedit.html
@@ -0,0 +1,134 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Preferences</al-setarg>
+ <table class="widelabelform">
+  <tbody>
+   <tr>
+    <th colspan="3">
+     Searching
+    </th>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="preferphonetic">Prefer phonetic</label></td>
+    <td id="preferphonetic" class="autowidth">
+     <al-input type="radio" name="phonetic_search" value="True"> Yes
+     <al-input type="radio" name="phonetic_search" value="False"> No
+    </td>
+    <td><al-input type="submit" name="reset:phonetic_search"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="resultsperpage">Results per page</label></td>
+    <td id="resultsperpage" class="autowidth">
+     <al-input type="radio" name="results_per_page" value="10"> 10
+     <al-input type="radio" name="results_per_page" value="25"> 25
+     <al-input type="radio" name="results_per_page" value="50"> 50
+    </td>
+    <td><al-input type="submit" name="reset:results_per_page"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <th colspan="3">
+     Data entry
+    </th>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="datestyle">Date style</label></td>
+    <td id="datestyle" class="autowidth">
+     <al-input type="radio" name="date_style" value="DMY"> DD/MM/YYYY
+     <al-input type="radio" name="date_style" value="MDY"> MM/DD/YYYY
+     <al-input type="radio" name="date_style" value="ISO"> YYYY-MM-DD
+    </td>
+    <td><al-input type="submit" name="reset:date_style"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <th colspan="3">
+     User Interface
+    </th>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="font_size">Font size</label></td>
+    <td id="font_size" class="autowidth" nowrap>
+     <al-input type="radio" name="font_size" value="8pt"> Extra Small
+     <al-input type="radio" name="font_size" value="9pt"> Small
+     <al-input type="radio" name="font_size" value="10pt"> Medium
+     <al-input type="radio" name="font_size" value="11pt"> Large
+     <al-input type="radio" name="font_size" value="13pt"> Extra Large
+    </td>
+    <td><al-input type="submit" name="reset:font_size"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="calendar">Pop-up calendar on date fields</label></td>
+    <td id="calendar" class="autowidth">
+     <al-input type="radio" name="jscalendar" value="True"> Yes
+     <al-input type="radio" name="jscalendar" value="False"> No
+    </td>
+    <td><al-input type="submit" name="reset:jscalendar"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="calendar">Intercept browser BACK button</label></td>
+    <td id="calendar" class="autowidth">
+     <al-input type="radio" name="nobble_back_button" value="True"> Yes
+     <al-input type="radio" name="nobble_back_button" value="False"> No
+    </td>
+    <td><al-input type="submit" name="reset:nobble_back_button"
+     class="butt" value="Reset"></td>
+   </tr>
+   <tr>
+    <th colspan="3">
+     Privacy
+    </th>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="privacy.share">Share contact details with other users</label></td>
+    <td class="autowidth">
+     <table>
+      <tr>
+       <td>
+        <al-input type="radio" name="privacy.share" value="True"> Yes<br>
+        <al-input type="radio" name="privacy.share" value="False"> No
+       </td>
+       <td class="label">
+        <label>Share:</label></td>
+       <td>
+        <al-for iter="p_i" expr="privacy.privopts">
+         <al-input type="checkbox" name="privacy.share_details" valueexpr="p_i.value()[0]" list /> <al-value expr="p_i.value()[1]" /><br>
+        </al-for>
+       <td>
+      </tr>
+     </table>
+    <script>radio_skip('privacy.share', 'False', 'privacy.share_details')</script>
+    </td>
+   </tr>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/prefsedit.py b/pages/prefsedit.py
new file mode 100644
index 0000000..9965a48
--- /dev/null
+++ b/pages/prefsedit.py
@@ -0,0 +1,65 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import user_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_reset(self, ctx, pref):
+        prefs = ctx.locals._credentials.prefs
+        prefs.reset(pref)
+        setattr(ctx.locals, pref, str(prefs.get(pref)))
+
+pageops = PageOps()
+
+
+pref_list = (
+    'phonetic_search',
+    'results_per_page',
+    'font_size',
+    'jscalendar',
+    'nobble_back_button',
+    'date_style',
+)
+
+def page_enter(ctx):
+    prefs = ctx.locals._credentials.prefs
+    for pref in pref_list:
+        setattr(ctx.locals, pref, str(prefs.get(pref)))
+        ctx.add_session_vars(pref)
+    user = ctx.locals._credentials.user
+    ctx.locals.privacy = user_search.PrivacyEdit(user.user_id)
+    ctx.add_session_vars('privacy')
+
+def page_display(ctx):
+    ctx.run_template('prefsedit.html')
+
+def page_process(ctx):
+    prefs = ctx.locals._credentials.prefs
+    for pref in pref_list:
+        prefs.set_from_str(pref, getattr(ctx.locals, pref))
+    if pageops.page_process(ctx):
+        return
+
+def page_leave(ctx):
+    if hasattr(ctx.locals, 'privacy'):
+        ctx.locals.privacy.update()
+    for pref in pref_list:
+        ctx.del_session_vars(pref)
+    ctx.del_session_vars('privacy')
diff --git a/pages/printform_inputs.html b/pages/printform_inputs.html
new file mode 100644
index 0000000..0b16f94
--- /dev/null
+++ b/pages/printform_inputs.html
@@ -0,0 +1,117 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-comment>
+    
+    This file needs to be kept in sync with app/pages/form_inputs.html.
+
+</al-comment>
+
+<al-macro name="tickbox">
+ <al-img class="tickbox" expr="appath('images/box.png')" />
+</al-macro>
+
+<al-macro name="printform_choices">
+   <al-if expr="input.choices">
+    <al-if expr="input.render_horizontal()">
+     <tr>
+      <td colspan="2">
+       <table cellpadding="0" cellspacing="0">
+        <tr>
+         <al-for iter="choice_i" expr="input.choices">
+          <al-exec expr="choice = choice_i.value()" />
+          <td>&#x2610;</td>
+          <td> <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" /> </td>
+         </al-for>
+        </tr>
+       </table>
+      </td>
+     </tr>
+    <al-else>
+     <al-for iter="choice_i" expr="input.choices">
+      <al-exec expr="choice = choice_i.value()" />
+      <tr>
+       <td valign="baseline">&#x2610;</td>
+       <td valign="baseline" width="100%">
+        <al-value expr="wiki_oneliner(choice[1])" noescape="noescape" />
+       </td>
+      </tr>
+     </al-for>
+    </al-if>
+    <al-exec expr="skips = input.skiptext()" />
+    <al-if expr="skips">
+     <al-for iter="skip_i" expr="skips">
+      <tr><td class="skiptext" colspan="2">
+       <al-value expr="wiki_oneliner(skip_i.value())" noescape="noescape" />.
+      </td></tr>
+     </al-for>
+    </al-if>
+   <al-else>
+    <tr><td colspan="2">[no choices defined]</td></tr>
+   </al-if>
+</al-macro>
+<al-lookup name="printform_inputs">
+
+ <!-- RadioList -->
+ <al-item expr="'RadioList'">
+  <tr><td colspan="2" class="pretext">(Mark only one box)</td></tr>
+  <al-expand name="printform_choices" />
+ </al-item>
+
+ <!-- DropList -->
+ <al-item expr="'DropList'">
+  <tr><td colspan="2" class="pretext">(Mark only one box)</td></tr>
+  <al-expand name="printform_choices" />
+ </al-item>
+
+ <!-- TextInput -->
+ <al-item expr="'TextInput'">
+  <tr>
+   <td colspan="2">
+    <div class="textbox" style="border-bottom: 1px solid black; height: 1.5em;"> </div>
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- DateInput -->
+ <al-item expr="'DateInput'">
+  <tr>
+   <td colspan="2">
+    <div class="textbox" style="border-bottom: 1px solid black; height: 1.5em;"> </div>
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- TextArea -->
+ <al-item expr="'TextArea'">
+  <al-for iter="li" expr="range(5)">
+   <tr>
+    <td colspan="2">
+     <div class="textlines" style="border-bottom: 1px solid black; height: 1.5em;"> </div>
+    </td>
+   </tr>
+  </al-for>
+ </al-item>
+
+ <!-- CheckBoxes -->
+ <al-item expr="'CheckBoxes'">
+  <al-expand name="printform_choices" />
+ </al-item>
+</al-lookup>
diff --git a/pages/printforms.html b/pages/printforms.html
new file mode 100644
index 0000000..fa7186d
--- /dev/null
+++ b/pages/printforms.html
@@ -0,0 +1,133 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="pline">
+ <tr><td colspan="3" class="pline"></td></tr>
+</al-macro>
+
+<al-include name="printform_inputs.html" />
+
+<al-lookup name="pelement_markup">
+
+ <!-- Question -->
+ <al-item expr="'Question'">
+  <al-if expr="not node.disabled">
+   <al-exec expr="inputs = node.get_inputs()" />
+   <tr>
+    <td class="question number"><al-value expr="node.label" /></td>
+    <al-if expr="inputs">
+     <td class="question">
+    <al-else>
+     <td class="question" colspan="2">
+    </al-if>
+     <al-for iter="skiptext_i" expr="node.skiptext()">
+      <div class="skiptext"><al-value expr="wiki_oneliner(skiptext_i.value())" noescape></div>
+     </al-for>
+     <al-value expr="wiki_text(node.text)" noescape="noescape" />
+     <al-if expr="node.help">
+      <hr width="80%">
+      <div class="info">
+        <al-value expr="wiki_text(node.help)" noescape="noescape" />
+      </div>
+     </al-if>
+    </td>
+    <al-if expr="inputs">
+     <td class="inputs">
+      <table width="100%" border="0" cellspacing="0">
+       <al-for iter="input_i" expr="inputs">
+        <al-exec expr="input = input_i.value()">
+        <!-- render input -->
+        <al-if expr="hasattr(input, 'pre_text') and input.pre_text">
+         <tr><td class="pretext" colspan="2">
+          <al-value expr="wiki_oneliner(input.pre_text)" noescape="noescape" />
+         </td></tr>
+        </al-if>
+        <al-value lookup="printform_inputs" expr="input.render" whitespace />
+        <al-if expr="hasattr(input, 'post_text') and input.post_text">
+         <tr><td class="posttext" colspan="2">
+          <al-value expr="wiki_oneliner(input.post_text)" noescape="noescape" />
+         </td></tr>
+        </al-if>
+       </al-for>
+      </table>
+     </td>
+    </al-if>
+   </tr>
+   <al-expand name="pline" />
+  </al-if>
+ </al-item>
+
+ <!-- SubSection -->
+ <al-item expr="'SubSection'">
+  <tr>
+   <td class="subsection number">
+    <al-value expr="node.label" />
+   </td>
+   <td class="subsection" colspan="2" width="100%">
+    <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- Section -->
+ <al-item expr="'Section'">
+  <tr>
+   <td class="section number">
+    <al-value expr="node.label" />
+   </td>
+   <td class="section" colspan="2" width="100%">
+    <al-value expr="wiki_oneliner(node.text)" noescape="noescape" />
+   </td>
+  </tr>
+ </al-item>
+
+ <!-- Form -->
+ <al-item expr="'Form'">
+  <tr>
+   <td colspan="3" class="heading">
+    <al-value expr="node.text" />
+    <al-if expr="node.version">
+     <div class="info">
+      (Version <al-value expr="node.version">)
+     </div>
+    </al-if>
+   </td>
+  </tr>
+ </al-item>
+
+ <al-item expr="'PagedForm'">
+   <al-value lookup="pelement_markup" expr="'Form'" whitespace />
+ </al-item>
+
+</al-lookup>
+
+<al-expand name="print_layout">
+  <al-setarg name="title"></al-setarg>
+  <al-for iter="forms_i" expr="forms.selected_forms()">
+   <al-exec expr="form = forms_i.value().get_form_ui()" />
+   <table class="syndform" border="0" cellspacing="0" cellpadding="0">
+    <al-tree iter="tree_i" expr="form">
+     <al-exec expr="node = tree_i.value()" />
+     <al-value lookup="pelement_markup" expr="node.render" whitespace />
+     <al-expand name="pline" />
+    </al-tree>
+   </table>
+  </al-for>
+</al-expand>
diff --git a/pages/printforms.py b/pages/printforms.py
new file mode 100644
index 0000000..b06fb29
--- /dev/null
+++ b/pages/printforms.py
@@ -0,0 +1,25 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+import config
+
+def page_display(ctx):
+    ctx.run_template('printforms.html')
+
+page_process = page_common.page_process
diff --git a/pages/report_columns.html b/pages/report_columns.html
new file mode 100644
index 0000000..bee5f18
--- /dev/null
+++ b/pages/report_columns.html
@@ -0,0 +1,161 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="reportcolselect">
+ <al-for iter="col_i" expr="outgroup">
+  <tr>
+   <td></td>
+   <td width="28" valign="middle">
+     <al-if expr="col_i.index()">
+      <al-input type="image" alt="up"
+            srcexpr="appath('images/button-up.png')"
+            nameexpr="'col:up:%s:%s' % (og_idx, col_i.index())" />
+     <al-else>
+      <al-img alt="nil" expr="appath('images/button-nil.png')" />
+     </al-if>
+     <al-if expr="col_i.index() < len(outgroup) - 1">
+      <al-input type="image" alt="down"
+            srcexpr="appath('images/button-down.png')"
+            nameexpr="'col:dn:%s:%s' % (og_idx, col_i.index())" />
+     </al-if>
+   </td>
+   <td><al-value expr="col_i.value().canonical_label()" /></td>
+   <td><al-input nameexpr="'reportparams.outgroups[%s][%s].label' % (og_idx, col_i.index())" /></td>
+   <td>
+    <al-input type="submit" class="butt" 
+    nameexpr="'col:del:%s:%s' % (og_idx, col_i.index())" value="Delete" />
+   </td>
+  </tr>
+ </al-for>
+ <tr>
+  <td colspan="4" align="right">
+   <al-select nameexpr="'reportparams.outgroups[%s].addcol' % og_idx" 
+    onchange="submit();" optionexpr="outgroup.available_cols()" />
+  </td>
+  <td>
+   <al-input type="submit" class="butt" 
+    nameexpr="'col:add:%s' % og_idx" value="Add" />
+  </td>
+ </tr>
+</al-macro>
+
+<al-macro name="reportdemogcols">
+ <table>
+  <al-for iter="og_i" expr="reportparams.outgroups">
+   <al-exec expr="outgroup = og_i.value()" />
+   <al-exec expr="og_idx = og_i.index()" />
+   <al-if expr="outgroup.is_caseperson">
+    <tr> 
+     <td width="28" valign="middle">
+       <al-if expr="reportparams.has_up(og_idx)">
+        <al-input type="image" alt="up"
+              srcexpr="appath('images/button-up.png')"
+              nameexpr="'col:gup:%s' % (og_idx)" />
+       <al-else>
+        <al-img alt="nil" expr="appath('images/button-nil.png')" />
+       </al-if>
+       <al-if expr="reportparams.has_dn(og_idx)">
+        <al-input type="image" alt="down"
+              srcexpr="appath('images/button-down.png')"
+              nameexpr="'col:gdn:%s' % (og_idx)" />
+       </al-if>
+     </td>
+     <th colspan="3"><al-value expr="outgroup.initial_label" /></th>
+     <td>
+      <al-if expr="og_idx">
+       <al-input type="submit" class="butt" 
+        nameexpr="'col:gdel:%s' % og_idx" value="Delete Row" />
+      <al-else>
+       <al-input type="submit" class="butt" 
+        nameexpr="'col:clear:%s' % og_idx" value="Clear All" />
+      </al-if>
+     </td>
+    </tr>
+    <al-if expr="og_idx > 0">
+     <tr>
+      <td></td>
+      <td colspan="3">
+       <al-input type="text" nameexpr="'reportparams.outgroups[%s].label' % og_idx" style="width: 100%;" />
+      </td>
+     </tr>
+    </al-if>
+    <al-expand name="reportcolselect" />
+
+    <tr><td colspan="5"><hr></td></tr>
+   </al-if>
+  </al-for>
+  <tr>
+   <td align="right" colspan="4"></td>
+   <td><al-input type="submit" class="butt" name="addcaseperson" value="Add" /></td>
+  </tr>
+ </table>
+</al-macro>
+
+<al-macro name="reportformcols">
+ <al-if expr="reportparams.available_col_forms()">
+  <table>
+   <al-for iter="og_i" expr="reportparams.outgroups">
+    <al-exec expr="outgroup = og_i.value()" />
+    <al-exec expr="og_idx = og_i.index()" />
+    <al-if expr="outgroup.is_form">
+     <tr> 
+      <td width="28" valign="middle">
+        <al-if expr="reportparams.has_up(og_idx)">
+         <al-input type="image" alt="up"
+               srcexpr="appath('images/button-up.png')"
+               nameexpr="'col:gup:%s' % (og_idx)" />
+        <al-else>
+         <al-img alt="nil" expr="appath('images/button-nil.png')" />
+        </al-if>
+        <al-if expr="reportparams.has_dn(og_idx)">
+         <al-input type="image" alt="down"
+               srcexpr="appath('images/button-down.png')"
+               nameexpr="'col:gdn:%s' % (og_idx)" />
+        </al-if>
+      </td>
+      <th colspan="3" align="left">
+       <al-value expr="outgroup.initial_label" />
+      </th>
+      <td><al-input type="submit" class="butt" 
+        nameexpr="'col:gdel:%s' % og_idx" value="Delete Form" /></td>
+     </tr>
+     <tr>
+      <td></td>
+      <td colspan="3">
+       <al-input type="text" nameexpr="'reportparams.outgroups[%s].label' % og_idx" style="width: 100%;" />
+      </td>
+     </tr>
+     <al-expand name="reportcolselect" />
+
+     <tr><td colspan="5"><hr></td></tr>
+    </al-if>
+   </al-for>
+   <tr>
+    <td align="right" colspan="4">
+     <al-select name="add_form" onchange="submit();"
+      optionexpr="reportparams.available_col_forms()" />
+    </td>
+    <td><al-input type="submit" class="butt" name="addformbutt" value="Add" /></td>
+   </tr>
+  </table>
+ <al-else>
+  <b>No forms are available</b>
+ </al-if>
+</al-macro>
diff --git a/pages/report_contactvis.html b/pages/report_contactvis.html
new file mode 100644
index 0000000..5df45f0
--- /dev/null
+++ b/pages/report_contactvis.html
@@ -0,0 +1,47 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+ <table width="100%" class="rfields">
+  <tr>
+   <th><label for="vismode">Visualisation mode</label></th>
+   <td>
+    <al-select id="vismode" name="reportparams.vismode" 
+     optionexpr="reportparams.vismode_options()" />
+   </td>
+  </tr>
+
+  <tr>
+   <th><label for="labelwith">Label with</label></th>
+   <td>
+    <al-select id="labelwith" name="reportparams.labelwith" 
+     optionexpr="reportparams.labelwith_options()" />
+   </td>
+  </tr>
+
+  <tr>
+   <th><label for="outputtype">Output type</label></th>
+   <td>
+    <al-select id="outputtype" name="reportparams.outputtype" 
+     optionexpr="reportparams.outputtype_options()" />
+   </td>
+  </tr>
+
+ </table>
+
diff --git a/pages/report_crosstab.html b/pages/report_crosstab.html
new file mode 100644
index 0000000..96767c6
--- /dev/null
+++ b/pages/report_crosstab.html
@@ -0,0 +1,67 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="crosstab.title" /></al-setarg>
+ <div class="smaller">Report generated: <al-value expr="crosstab.date" /></div>
+ <table class="crosstab clicktab" id="crosstab">
+  <al-for vars="page, page_label" iter="page_i" expr="crosstab.page.options">
+   <al-if expr="1 or not crosstab.suppress_empty_pages or crosstab.tally.get((TOTAL, TOTAL, page))">
+    <al-if expr="page_label">
+     <tr>
+      <al-th class="pagehead" colspanexpr="len(crosstab.col.options)+1">
+       <al-value expr="crosstab.page.label" />: <al-value expr="page_label">
+      </al-th>
+     </tr>
+    </al-if>
+    <tr>
+     <th> </th>
+     <al-th class="colhead" colspanexpr="len(crosstab.col.options)">
+      <al-value expr="crosstab.col.label" />
+     </al-th>
+    </tr>
+    <tr>
+     <th class="rowhead r b"><al-value expr="crosstab.row.label" /></th>
+     <al-for vars="col, col_label" expr="crosstab.col.options">
+      <al-th classexpr="crosstab.style(HEAD, col)">
+       <al-value expr="col_label">
+      </al-th>
+     </al-for>
+    </tr>
+    <al-for vars="row, row_label" iter="row_i" expr="crosstab.row.options">
+     <tr>
+      <al-th classexpr="crosstab.style(row, HEAD)">
+       <al-value expr="row_label">
+      </al-th>
+      <al-for vars="col, col_label" iter="col_i" expr="crosstab.col.options">
+       <al-td idexpr="'key:%d:%d:%d' % (row_i.index(), col_i.index(), page_i.index())" 
+              classexpr="crosstab.style(row, col)">
+        <al-value expr="crosstab.tally.get((row, col, page), '')" />
+       </al-td>
+      </al-for>
+     </tr>
+    </al-for>
+   </al-if>
+  </al-for>
+ </table>
+ <al-input type="hidden" name="key" value="" />
+ <script>clicktab('crosstab', 'appform');</script>
+ 
+</al-expand>
diff --git a/pages/report_crosstab.py b/pages/report_crosstab.py
new file mode 100644
index 0000000..360dab0
--- /dev/null
+++ b/pages/report_crosstab.py
@@ -0,0 +1,40 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, syndrome, demogfields
+from casemgr.reports.crosstab import TOTAL, HEAD
+from pages import page_common, caseset_ops
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_key(self, ctx, *coords):
+        case_ids = ctx.locals.crosstab.get_key_case_ids(*coords)
+        cell_desc = ctx.locals.crosstab.desc_key(*coords)
+        caseset_ops.make_caseset(ctx, case_ids, cell_desc)
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, crosstab):
+    ctx.locals.crosstab = crosstab
+    ctx.add_session_vars('crosstab')
+
+def page_leave(ctx):
+    ctx.del_session_vars('crosstab')
+
+def page_display(ctx):
+    ctx.run_template('report_crosstab.html')
diff --git a/pages/report_edit.html b/pages/report_edit.html
new file mode 100644
index 0000000..59fc7c1
--- /dev/null
+++ b/pages/report_edit.html
@@ -0,0 +1,235 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="tabs.html">
+<al-include name="report_columns.html">
+
+<al-macro name="report_info">
+ <table class="rfields">
+  <tr>
+   <th><label for="name">Name</label></th>
+   <td class="wide"><al-input id="name" name="reportparams.label" /></td>
+  </tr>
+  <tr>
+   <th><label for="header">Title</label></th>
+   <td class="wide"><al-input id="header" name="reportparams.header" width="80" /></td>
+  </tr>
+  <tr>
+   <th><label for="sharing">Type</label></th>
+   <td>
+    <al-select name="set_type" expr="reportparams.report_type"
+      optionexpr="reports.report_type_optionexpr()" 
+      onchange="submit();" />
+   </td>
+  </tr>
+  <tr>
+   <th><label for="sharing">Sharing</label></th>
+   <td>
+    <al-for vars="name, label" expr="reportparams.sharing_options">
+     <al-input type="radio" name="reportparams.sharing" valueexpr="name" />
+      <al-value expr="label" />  
+    </al-for>
+   </td>
+  </tr>
+  <al-if expr="delete_confirm">
+   <tr>
+    <td colspan="2" class="danger">
+     Delete this report?
+     <al-input type="submit" class="butt"
+      name="delete_cancel" value="Cancel" />
+     <al-input type="submit" class="butt"
+      name="delete_confirm" value="Delete" />
+    </td>
+   </tr>
+  <al-else>
+   <al-if expr="reportparams.show_headfoot">
+    <tr>
+     <th><label for="preamble">Preamble</label>
+      <br><al-expand name="wikihelp" /></th>
+     <td class="wide"><al-textarea id="preamble" name="reportparams.preamble" 
+         rows="4" cols="80" /></td>
+    </tr>
+    <tr>
+     <th><label for="footer">Footer</label>
+      <br><al-expand name="wikihelp" /></th>
+     <td class="wide"><al-textarea id="footer" name="reportparams.footer" 
+         rows="4" cols="80" /></td>
+    </tr>
+   </al-if>
+   <tr>
+    <td>
+     <al-if expr="reportparams.loaded_from_id">
+       <al-input type="submit" class="danger butt" 
+         name="delete" value="Delete" />
+     </al-if>
+    </td>
+    <td>
+     <al-input type="submit" name="params_download" value="Download" class="butt">
+    </td>
+   </tr>
+  </al-if>
+ </table>
+</al-macro>
+
+<al-macro name="report_crosstab">
+ <table class="minselect">
+  <tr>
+   <td><label>Columns</label></td>
+   <td>
+    <al-select name="reportparams.col.form_name" onchange="submit();"
+     optionexpr="reportparams.col.form_options()" />
+   </tr>
+  </tr>
+  <tr>
+   <td></td>
+   <td>
+    <al-if expr="reportparams.col.show_fields()">
+     <al-select name="reportparams.col.field"
+      optionexpr="reportparams.col.col_options()" />
+    </al-if>
+   </tr>
+  </tr>
+  <tr>
+   <td><label>Rows</label></td>
+   <td>
+    <al-select name="reportparams.row.form_name" onchange="submit();"
+     optionexpr="reportparams.row.form_options()" />
+   </tr>
+  </tr>
+  <tr>
+   <td></td>
+   <td>
+    <al-if expr="reportparams.row.show_fields()">
+     <al-select name="reportparams.row.field"
+      optionexpr="reportparams.row.col_options()" />
+    </al-if>
+   </tr>
+  </tr>
+  <tr>
+   <td><label>Pages</label></td>
+   <td>
+    <al-select name="reportparams.page.form_name" onchange="submit();"
+     optionexpr="reportparams.page.form_options()" />
+   </tr>
+  </tr>
+  <tr>
+   <td></td>
+   <td>
+    <al-if expr="reportparams.page.show_fields()">
+     <al-select name="reportparams.page.field"
+      optionexpr="reportparams.page.col_options()" />
+    </al-if>
+   </tr>
+  </tr>
+  <tr>
+   <td><label>Include empty rows & columns</label></td>
+   <td>
+    <al-input id="empty_rowsncols_no" type="radio" name="reportparams.empty_rowsncols" value="False" /><label for="empty_rowsncols_no"> No</label>  <al-input id="empty_rowsncols_yes" type="radio" name="reportparams.empty_rowsncols" value="True" /><label for="empty_rowsncols_yes"> Yes
+   </td>
+  </tr>
+  <tr>
+   <td><label>Include empty pages</label></td>
+   <td>
+    <al-input id="empty_pages_no" type="radio" name="reportparams.empty_pages" value="False" /><label for="empty_pages_no"> No</label>  <al-input id="empty_pages_yes" type="radio" name="reportparams.empty_pages" value="True" /><label for="empty_pages_yes"> Yes
+   </td>
+  </tr>
+ </table>
+</al-macro>
+
+<al-macro name="report_export">
+ <table class="rfields">
+  <tr>
+   <th>
+    <label for="strip_newlines">Replace newlines in fields with spaces?</label>
+   </th>
+   <td>
+     <label><al-input type="radio" name="reportparams.export_strip_newlines"
+      value="yes"  /> Yes</label>  <label><al-input type="radio"
+      name="reportparams.export_strip_newlines" value="no" /> No</label>
+   </td>
+  </tr>
+  <tr>
+   <th><label for="column_labels">Column labels</label></th>
+   <td>
+    <label><al-input type="radio" name="reportparams.export_column_labels"
+     value="fields" /> Field labels</label><br>
+    <label><al-input type="radio" name="reportparams.export_column_labels"
+     value="dbcols" /> Database labels</label><br>
+   </td>
+  </tr>
+  <tr>
+   <th><label for="row_type">Rows</label></th>
+   <td>
+    <label><al-input type="radio" name="reportparams.export_row_type"
+     value="cases" /> by case (with flattened forms)</label><br>
+    <label><al-input type="radio" name="reportparams.export_row_type"
+     value="forms" /> by form (with case)</label><br>
+   </td>
+  </tr>
+  <tr>
+   <td></td>
+   <td>
+    <al-input type="submit" class="butt" name="export" value="Export" />
+   </td>
+  </tr>
+ </table>
+</al-macro>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="reportparams.type_label" /></al-setarg>
+ <al-exec expr="tabs = report_tabs" />
+ <al-expand name="left-tabs">
+    <al-lookup expr="report_tabs.selected">
+     <al-item expr="'info'">
+      <al-expand name="report_info" />
+     </al-item>
+     <al-item expr="'filters'">
+      <al-include name="report_filters.html" />
+     </al-item>
+     <al-item expr="'columns'">
+      <al-expand name="reportdemogcols" />
+     </al-item>
+     <al-item expr="'crosstab'">
+      <al-expand name="report_crosstab" />
+     </al-item>
+     <al-item expr="'forms'">
+      <al-expand name="reportformcols" />
+     </al-item>
+     <al-item expr="'orderby'">
+      <al-include name="report_orderby.html" />
+     </al-item>
+     <al-item expr="'epicurve'">
+      <al-include name="report_epicurve.html" />
+     </al-item>
+     <al-item expr="'contactvis'">
+      <al-if expr="reports.have_graphviz()">
+       <al-include name="report_contactvis.html" />
+      <al-else>
+       <div class="redbox">
+        Association visualisation not available on this system
+       </div>
+      </al-if>
+     </al-item>
+     <al-item expr="'export'">
+      <al-expand name="report_export" />
+     </al-item>
+    </al-lookup>
+ </al-expand>
+</al-expand>
diff --git a/pages/report_edit.py b/pages/report_edit.py
new file mode 100644
index 0000000..e45d466
--- /dev/null
+++ b/pages/report_edit.py
@@ -0,0 +1,217 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+from cocklebur.filename_safe import filename_safe
+from casemgr import globals, caseaccess, cases, reports
+from casemgr.tabs import Tabs
+from pages import page_common, caseset_ops, report_ops
+import config
+
+
+def cancel_add_filter(ctx):
+    if ctx.locals.add_filter:
+        ctx.locals.add_filter.abort()
+        ctx.locals.add_filter = None
+
+def add_filter(ctx):
+    if ctx.locals.add_filter and ctx.locals.add_filter.is_complete():
+        filter = ctx.locals.add_filter.add()
+        if hasattr(filter, 'children'):
+            ctx.locals.add_filter = ctx.locals.reportparams.filter_adder(filter)
+            ctx.locals.edit_filter = ctx.locals.add_filter.placeholder
+        else:
+            ctx.locals.edit_filter = filter
+            ctx.locals.add_filter = None
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def check_unsaved_or_confirmed(self, ctx):
+        ctx.locals.reportparams.autosave(ctx.locals._credentials)
+        globals.db.commit()
+
+    def do_col(self, ctx, op, group_index, col_index=None):
+        group_index = int(group_index)
+        if col_index:
+            col_index = int(col_index)
+        ctx.locals.reportparams.colop(op, group_index, col_index)
+
+    def do_addcaseperson(self, ctx, ignore):
+        ctx.locals.reportparams.add_caseperson()
+
+    def do_add_order(self, ctx, ignore):
+        ctx.locals.reportparams.add_order()
+
+    def do_del_order(self, ctx, index):
+        ctx.locals.reportparams.del_order(int(index))
+
+    def do_save(self, ctx, ignore):
+        if (not ctx.locals.reportparams.label or 
+                not ctx.locals.reportparams.label.strip()):
+            ctx.locals.report_tabs.select('info')
+            raise page_common.PageError('A report name must be specified')
+        ctx.locals.reportparams.save(ctx.locals._credentials)
+        globals.db.commit()
+        ctx.add_message('Report parameters saved')
+
+    def do_delete(self, ctx, ignore):
+        ctx.locals.delete_confirm = True
+
+    def do_delete_cancel(self, ctx, ignore):
+        ctx.locals.delete_confirm = False
+
+    def do_delete_confirm(self, ctx, ignore):
+        if ctx.locals.reportparams.loaded_from_id:
+            reports.delete(ctx.locals.reportparams.loaded_from_id)
+            globals.db.commit()
+        ctx.locals.delete_confirm = False
+        ctx.locals.reportparams.autosave(ctx.locals._credentials)
+        ctx.pop_page()
+
+    def do_tab(self, ctx, pagetab):
+        ctx.locals.delete_confirm = False
+        ctx.locals.report_tabs.select(pagetab)
+
+    def do_report(self, ctx, ignore):
+        report_ops.run_report(ctx, ctx.locals.reportparams)
+
+    def do_edit(self, ctx, ignore):
+        case_ids = ctx.locals.reportparams.get_case_ids(ctx.locals._credentials)
+        caseset_ops.make_caseset(ctx, case_ids, 'Report cases')
+
+    def do_export(self, ctx, ignore):
+        report_ops.report_export(ctx, ctx.locals.reportparams)
+
+    def do_params_download(self, ctx, ignore):
+        if ctx.locals.reportparams.label:
+            name = filename_safe(ctx.locals.reportparams.label)
+        else:
+            name = 'reportparams'
+        downloader = page_common.download(ctx, name + '.xml')
+        ctx.locals.reportparams.xmlsave(downloader)
+
+    def do_filteradd(self, ctx, path):
+        cancel_add_filter(ctx)
+        parent = ctx.locals.reportparams.path_filter(path)
+        ctx.locals.add_filter = ctx.locals.reportparams.filter_adder(parent)
+        ctx.locals.edit_filter = ctx.locals.add_filter.placeholder
+
+    def do_filteraddexpr(self, ctx, ignore):
+        add_filter(ctx)
+
+    def do_filteredit(self, ctx, path):
+        cancel_add_filter(ctx)
+        filter = ctx.locals.reportparams.path_filter(path)
+        if filter.op == 'placeholder':
+            # Whoops, old placeholder - delete and go back into add filter mode
+            # This shouldn't happen, but if it does, try to be nice about it.
+            path = '.'.join(path.split('.')[:-1])
+            parent = ctx.locals.reportparams.path_filter(path)
+            parent.children.remove(filter)
+            self.do_filteradd(ctx, path)
+        else:
+            ctx.locals.add_filter = None
+            ctx.locals.edit_filter = ctx.locals.reportparams.path_filter(path)
+
+    def do_filterdelete(self, ctx, ignore):
+        if ctx.locals.edit_filter:
+            if ctx.locals.reportparams.del_filter(ctx.locals.edit_filter):
+                ctx.locals.edit_filter = None
+
+    def do_filterclose(self, ctx, ignore):
+        cancel_add_filter(ctx)
+        ctx.locals.edit_filter = None
+
+    def do_filterconj(self, ctx, path):
+        filter = ctx.locals.reportparams.path_filter(path)
+        filter.toggle_conj()
+
+pageops = PageOps()
+
+def default_tab(ctx):
+    ctx.locals.report_tabs.select('info')
+
+
+def mktabs(report_params):
+    tabs = Tabs()
+    tabs.add('info', 'Info')
+    if report_params.show_filters:
+        tabs.add('filters', 'Filters')
+    if report_params.show_orderby:
+        tabs.add('orderby', 'Order By')
+    if report_params.show_filters:
+        tabs.add('edit', 'Cases', action=True, accesskey='e')
+    if report_params.show_columns:
+        tabs.spacer()
+        tabs.add('columns', 'Columns')
+        tabs.add('forms', 'Forms')
+    if report_params.show_axes:
+        tabs.add('crosstab', 'Axes')
+    if report_params.show_epicurve:
+        tabs.add('epicurve', 'Time series')
+    if report_params.show_contactvis:
+        tabs.add('contactvis', 'Params')
+    if report_params.show_filters and report_params.show_columns:
+        tabs.add('export', 'Export', accesskey='x')
+    tabs.add('report', 'Report', action=True, accesskey='r')
+    tabs.spacer()
+    tabs.add('save', 'Save', action=True, accesskey='s')
+    return tabs.done()
+
+
+def reset(ctx):
+    default_tab(ctx)
+    ctx.locals.delete_confirm = False
+    ctx.locals.caseset = None
+
+
+def page_enter(ctx, reportparams):
+    ctx.locals.reportparams = reportparams
+    ctx.locals.report_tabs = mktabs(reportparams)
+    ctx.locals.edit_filter = None
+    ctx.locals.add_filter = None
+    reset(ctx)
+    if ctx.locals.reportparams.loaded_from_id is not None:
+        ctx.add_messages(ctx.locals.reportparams.check())
+    ctx.add_session_vars('report_tabs', 'reportparams', 'delete_confirm',
+                         'edit_filter', 'add_filter')
+
+def page_leave(ctx):
+    ctx.del_session_vars('report_tabs', 'reportparams', 'delete_confirm',
+                         'edit_filter', 'add_filter')
+
+def page_display(ctx):
+    if not page_common.send_download(ctx):
+        ctx.run_template('report_edit.html')
+
+
+def page_process(ctx):
+    params = ctx.locals.reportparams
+    if getattr(ctx.locals, 'add_form', None):
+        params.add_form(ctx.locals.add_form)
+    add_filter(ctx)
+    ctx.locals.add_form = None
+    params.cols_update()
+    set_type = getattr(ctx.locals, 'set_type', None)
+    if set_type and set_type != params.report_type:
+        ctx.locals.reportparams = params.change_type(set_type, msgs=ctx)
+        ctx.locals.report_tabs = mktabs(ctx.locals.reportparams)
+    try:
+        pageops.page_process(ctx)
+    except reports.ReportParamError, e:
+        ctx.add_error(e)
diff --git a/pages/report_epicurve.html b/pages/report_epicurve.html
new file mode 100644
index 0000000..7295881
--- /dev/null
+++ b/pages/report_epicurve.html
@@ -0,0 +1,86 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+ <table width="100%" class="rfields">
+  <tr>
+   <th><label for="ts_join">By</label></th>
+   <td><al-select id="ts_join" name="reportparams.ts_join" 
+         class="rmw" onchange="submit();"
+         optionexpr="reportparams.available_forms()" /></td>
+  </tr>
+  <al-if expr="reportparams.ts_join">
+   <tr>
+    <th><label for="ts_missing_forms">Include records with no form</label></th>
+    <td>
+     <al-input type="radio" name="reportparams.ts_missing_forms" value="True"> Yes  <al-input type="radio" name="reportparams.ts_missing_forms" value="False"> No
+    </td>
+   </tr>
+  </al-if>
+  <tr>
+   <th><label for="ts_bincol">Timeseries by</label></th>
+   <td><al-select id="ts_bincol" name="reportparams.ts_bincol" 
+         class="rmw"
+         optionexpr="reportparams.available_bincol()" /></td>
+  </tr>
+  <tr>
+   <th><label for="ts_bincol2">2nd panel</label></th>
+   <td><al-select id="ts_bincol2" name="reportparams.ts_bincol2" 
+         class="rmw"
+         optionexpr="reportparams.available_bincol(1)" /></td>
+  </tr>
+  <tr>
+   <th><label for="ts_nbins">Bins</label></th>
+   <td><al-select id="ts_nbins" name="reportparams.ts_nbins" 
+         class="rmw"
+         optionexpr="reportparams.available_nbins()" /></td>
+  </tr>
+  <tr>
+   <th><label for="ts_stacking">Stacking</label></th>
+   <td><al-select id="ts_stacking" name="reportparams.ts_stacking" 
+         class="rmw" onchange="submit();"
+         optionexpr="reportparams.available_strata()" /></td>
+  </tr>
+  <tr>
+   <th><label for="ts_stack_ratios">Ratios</label></th>
+   <td>
+    <al-input type="radio" name="reportparams.ts_stack_ratios" value="True"> Yes  <al-input type="radio" name="reportparams.ts_stack_ratios" value="False"> No
+   </td>
+  </tr>
+  <al-if expr="reportparams.ts_stacking">
+   <tr>
+    <th><label for="ts_stack_suppress">Suppress</label></th>
+    <td>
+     <al-for vars="name, label" expr="reportparams.available_values(reportparams.ts_stacking)">
+      <span style="white-space: nowrap; min-width: 10em;">
+       <al-input type="checkbox" name="reportparams.ts_stack_suppress" list 
+        valueexpr="name" idexpr="'ts_suppress_' + name"/>
+       <al-label forexpr="'ts_suppress_' + name"> <al-value expr="label" /></al-label>
+      </span>
+     </al-for>
+    </td>
+   </tr>
+  </al-if>
+  <tr>
+   <th><label for="ts_outfmt">Output format</label></th>
+   <td><al-select id="ts_outfmt" name="reportparams.ts_outfmt" 
+         class="rmw"
+         optionexpr="reportparams.available_outfmts()" /></td>
+  </tr>
+ </table>
diff --git a/pages/report_filters.html b/pages/report_filters.html
new file mode 100644
index 0000000..a116257
--- /dev/null
+++ b/pages/report_filters.html
@@ -0,0 +1,191 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-lookup name="report_filter">
+ <al-item expr="'checkboxes'">
+  <al-for iter="ci" expr="edit_filter.options()">
+   <al-input type="checkbox" name="edit_filter.values" list
+     valueexpr="ci.value()[0]" /> <al-value expr="ci.value()[1]" />
+       
+  </al-for>
+ </al-item>
+ <al-item expr="'select'">
+  <al-select name="edit_filter.value" optionexpr="edit_filter.options()" />
+ </al-item>
+ <al-item expr="'multiselect'">
+  <al-select name="edit_filter.values" multiple size="6"
+   optionexpr="edit_filter.options()" />
+ </al-item>
+ <al-item expr="'range'">
+  <table border="0">
+   <tr>
+    <td>From</td>
+    <td>
+     <al-if expr="edit_filter.date_format()">
+      <al-input type="text" calendarformatexpr="edit_filter.date_format()"
+       name="edit_filter.from_value" />
+     <al-else>
+      <al-input type="text" name="edit_filter.from_value" />
+     </al-if> </td>
+    <td nowrap>
+      Incl. from?</td>
+    <td><al-input type="checkbox" value="True"
+      name="edit_filter.incl_from" /></td>
+   </tr>
+   <tr>
+    <td>To</td>
+    <td>
+     <al-if expr="edit_filter.date_format()">
+      <al-input type="text" calendarformatexpr="edit_filter.date_format()"
+       name="edit_filter.to_value" />
+     <al-else>
+      <al-input type="text"
+       name="edit_filter.to_value" />
+     </al-if>
+    </td>
+    <td nowrap>
+      Incl. to?</td>
+    <td><al-input type="checkbox" value="True"
+      name="edit_filter.incl_to" /></td>
+   </tr>
+  </table>
+ </al-item>
+ <al-item expr="'pattern'">
+  <al-input type="text" name="edit_filter.value" />
+ </al-item>
+ <al-item expr="'commalist'">
+  <al-input type="text" name="edit_filter.value" />
+   (comma-separated list)
+ </al-item>
+ <al-item expr="'pattern'">
+  <al-input type="text" name="edit_filter.value" /><br>
+  <div class="smaller">(pattern: ? - any single character, * - any run of characters)</div>
+ </al-item>
+ <al-item expr="'phonetic'">
+  <al-input type="text" name="edit_filter.value" /> <al-input type="checkbox" name="edit_filter.phonetic" value="True"> Phonetic
+ </al-item>
+ <al-item expr="'caseset'">
+  Caseset: <al-value expr="edit_filter.caseset" />
+ </al-item>
+</al-lookup>
+
+<div id="filter_select">
+ <al-for vars="token, path, node" expr="reportparams.walk_filters()">
+  <al-lookup expr="token">
+   <al-item expr="'open'">
+    <div class="rfilter">
+     <span class="rconj"><al-value expr="node.op.upper()" /></span>
+   </al-item>
+   <al-item expr="'clause'">
+    <al-if expr="node is edit_filter">
+     <div class="rclause rselected">
+      <b><al-value expr="node.label()" /></b> <al-value expr="node.desc()" />
+     </div>
+    <al-else>
+     <al-div class="clickable rclause" idexpr="'filteredit:' + path">
+      <b><al-value expr="node.label()" /></b> <al-value expr="node.desc()" />
+     </al-div>
+    </al-if>
+   </al-item>
+   <al-item expr="'op'">
+    <div class="rop"><al-value expr="node.op.lower()" /></div>
+   </al-item>
+   <al-item expr="'close'">
+    <al-span class="clickable rop" idexpr="'filteradd:' + path">
+     add expression</al-span>
+    <al-span class="clickable rop" idexpr="'filterconj:' + path">
+     change conjunction</al-span>
+    </div>
+   </al-item>
+  </al-lookup>
+ </al-for>
+</div>
+<script>clicktab('filter_select', 'appform')</script>
+
+<al-if expr="add_filter">
+ <table class="edit-filter">
+  <tr>
+   <th>Group</th>
+   <td>
+    <al-select name="add_filter.group" optionexpr="add_filter.groups()" onchange="submit();" />
+   </td>
+  </tr>
+  <al-if expr="add_filter.has_field()">
+   <tr>
+    <th>Field</th>
+    <td>
+     <al-select name="add_filter.field" optionexpr="add_filter.fields()" onchange="submit();" />
+    </td>
+   </tr>
+   <al-if expr="add_filter.field">
+    <tr>
+     <th>Matching</th>
+     <td>
+      <al-select name="add_filter.op" optionexpr="add_filter.field_ops()" />
+     </td>
+    </tr>
+   </al-if>
+  </al-if>
+  <tr>
+   <td>
+    <al-input name="filterclose" type="submit" class="butt" value="Cancel" />
+   </td>
+   <td align="right">
+    <al-input name="filteraddexpr" type="submit" class="butt" value="Add" />
+   </td>
+  </tr>
+ </table>
+<al-elif expr="edit_filter">
+ <table class="edit-filter">
+  <tr>
+   <th nowrap>Edit Field</th>
+   <td>
+    <al-input class="danger right butt" name="filterdelete" type="submit" value="Delete" />
+    <al-value expr="edit_filter.label()" />
+   </td>
+  </tr>
+  <tr>
+   <th>Group</th>
+   <td>
+    <al-value expr="edit_filter.form_label()" />
+   </td>
+  </tr>
+  <al-if expr="edit_filter.allow_negate()">
+   <tr>
+    <th>Negate</th>
+    <td>
+     <al-input type="checkbox" value="True" name="edit_filter.negate" />
+    </td>
+   </tr>
+  </al-if>
+  <tr>
+   <th>Values</th>
+   <td>
+    <al-value expr="edit_filter.get_markup()" lookup="report_filter" />
+   </td>
+  </tr>
+  <tr>
+   <td></td>
+   <td align="right">
+    <al-input name="filterclose" type="submit" class="butt" value="Done" />
+   </td>
+  </tr>
+ </table>
+</al-if>
diff --git a/pages/report_image.html b/pages/report_image.html
new file mode 100644
index 0000000..6686db8
--- /dev/null
+++ b/pages/report_image.html
@@ -0,0 +1,35 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"></al-setarg>
+ <al-if expr="imagefile.endswith('.png')">
+  <al-img expr="appath('scratch', imagefile)" />
+ <al-elif expr="imagefile.endswith('.svg')">
+  <al-object dataexpr="appath('scratch', imagefile)" 
+   height="600" width="800" type="image/svg+xml" />
+ <al-elif expr="imagefile.endswith('.png')">
+  <al-object dataexpr="appath('scratch', imagefile)" type="application/pdf" />
+ <al-elif expr="imagefile.endswith('.ps')">
+  <al-object dataexpr="appath('scratch', imagefile)" type="application/postscript" />
+ </al-if>
+ <br>
+ <al-a expr="appath('scratch', imagefile)" type="application/unknown">Save As</al-a>
+</al-expand>
diff --git a/pages/report_image.py b/pages/report_image.py
new file mode 100644
index 0000000..001650c
--- /dev/null
+++ b/pages/report_image.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+
+from casemgr import globals
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    pass
+
+page_process = PageOps().page_process
+
+def page_enter(ctx, rpt):
+    ctx.locals.imagefile = rpt.imagefile
+    ctx.add_session_vars('imagefile')
+
+def page_leave(ctx):
+    imagefile = getattr(ctx.locals, 'imagefile', None)
+    if imagefile and not os.path.dirname(imagefile):
+        try:
+            os.unlink(os.path.join(config.scratchdir, imagefile))
+        except OSError:
+            pass
+        ctx.locals.imagefile = None
+    ctx.del_session_vars('imagefile')
+
+def page_display(ctx):
+    ctx.run_template('report_image.html')
diff --git a/pages/report_menu.html b/pages/report_menu.html
new file mode 100644
index 0000000..bef0ded
--- /dev/null
+++ b/pages/report_menu.html
@@ -0,0 +1,79 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Reports - <al-value expr="user_reports.syndrome_name" /></al-setarg>
+ <al-if expr="report_upload_mode">
+  <div class="centbox">
+   <h1>Upload an XML report</h1>
+   <al-input type="file" name="report_import_file" size="40" class="file">
+   <al-input type="submit" name="report_import" value="Upload" class="butt">
+   <al-input type="submit" name="report_import_cancel" value="Cancel" class="butt">
+  </div>
+ <al-else>
+  <al-if expr="can_edit">
+   <div class="reportbuttons">
+    <al-input type="submit" class="bigbutt" name="upload" value="Upload Report" />
+    <al-input type="submit" class="bigbutt" name="new" value="New Report" />
+   </div>
+  </al-if>
+  <al-if expr="user_reports">
+   <table id="reports" class="reportmenu">
+    <al-for vars="sharing" expr="reports.sharing_tags">
+     <al-exec expr="ur_sharing = user_reports.by_sharing[sharing]" />
+     <al-if expr="ur_sharing">
+      <tr>
+       <td colspan="2">
+        <h3><al-lookup expr="sharing">
+          <al-item expr="'private'">My reports</al-item>
+          <al-item expr="'unit'"><al-value expr="config.unit_label" /> reports</al-item>
+          <al-item expr="'public'">Public reports</al-item>
+          <al-item expr="'quick'">Quick (home page) reports</al-item>
+        </al-lookup></h3>
+       </td>
+      </tr>
+      <tr>
+       <al-exec expr="split = int(len(ur_sharing) / 2.0 + 0.5)" />
+       <al-for vars="offs" expr="[0, split]">
+        <td width="50%">
+         <ul>
+          <al-for vars="rt" expr="ur_sharing[offs:offs+split]">
+           <li>
+            <al-if expr="can_edit">
+             <al-input type="submit" class="sublink edit" value="edit"
+                nameexpr="'edit:%s' % rt.report_params_id" />
+            </al-if>
+            <al-span idexpr="'report:%s' % rt.report_params_id" class="clickable"><al-value expr="rt.label" /></al-span>
+            <span class="rtype"> (<al-value expr="reports.type_label(rt.type)" />)</span>
+           </li>
+          </al-for>
+         </ul>
+        </td>
+       </al-for>
+      </tr>
+     </al-if>
+    </al-for>
+   </table>
+   <script type="text/javascript">clicktab('reports', 'appform');</script>
+  <al-else>
+   <p>No reports available</p>
+  </al-if>
+ </al-if>
+</al-expand>
diff --git a/pages/report_menu.py b/pages/report_menu.py
new file mode 100644
index 0000000..6894911
--- /dev/null
+++ b/pages/report_menu.py
@@ -0,0 +1,75 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, reports
+from pages import page_common, report_ops
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_report(self, ctx, report_id):
+        if ctx.locals._credentials.rights.any('EXPORT', 'PUBREP'):
+            reportparams = reports.load(int(report_id),
+                                        ctx.locals._credentials)
+            report_ops.run_report(ctx, reportparams)
+
+    def do_edit(self, ctx, report_id):
+        if 'EXPORT' in ctx.locals._credentials.rights:
+            reportparams = reports.load(int(report_id),
+                                        ctx.locals._credentials)
+            ctx.push_page('report_edit', reportparams)
+
+    def do_new(self, ctx, ignore):
+        if 'EXPORT' in ctx.locals._credentials.rights:
+            reportparams = reports.new_report(ctx.locals.report_syndrome_id)
+            ctx.push_page('report_edit', reportparams)
+
+    def do_upload(self, ctx, ignore):
+        ctx.locals.report_upload_mode = True
+
+    def do_report_import_cancel(self, ctx, ignore):
+        ctx.locals.report_upload_mode = False
+
+    def do_report_import(self, ctx, ignore):
+        if 'EXPORT' not in ctx.locals._credentials.rights:
+            return
+        if len(ctx.locals.report_import_file) != 1:
+            raise page_common.PageError('Choose one file')
+        reportparams = reports.parse_file(ctx.locals.report_syndrome_id,
+                                          ctx.locals.report_import_file[0].file)
+        ctx.push_page('report_edit', reportparams)
+        ctx.locals.report_upload_mode = False
+
+
+pageops = PageOps()
+
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.report_syndrome_id = syndrome_id
+    ctx.locals.report_upload_mode = False
+    ctx.add_session_vars('report_syndrome_id', 'report_upload_mode')
+
+def page_leave(ctx):
+    ctx.del_session_vars('report_syndrome_id', 'report_upload_mode')
+
+def page_display(ctx):
+    ctx.locals.user_reports = reports.ReportMenu(ctx.locals._credentials,
+                                                 ctx.locals.report_syndrome_id)
+    ctx.locals.can_edit = 'EXPORT' in ctx.locals._credentials.rights
+    ctx.run_template('report_menu.html')
+
+page_process = pageops.page_process
diff --git a/pages/report_ops.py b/pages/report_ops.py
new file mode 100644
index 0000000..94a890d
--- /dev/null
+++ b/pages/report_ops.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import datetime
+from casemgr import reports
+from pages import page_common
+import config
+
+class ConfirmReport(page_common.Confirm):
+    mode = 'confirm'
+    title = 'Large report'
+    buttons = [
+        ('discard', 'No'),
+        ('continue', 'Yes'),
+    ]
+
+
+def run_report(ctx, reportparams):
+    try:
+        rpt = reportparams.report(ctx.locals._credentials, msgs=ctx)
+    except reports.ReportParamError, e:
+        ctx.add_error(e)
+    else:
+        if ctx.have_errors():
+            return
+        if not rpt:
+            raise reports.ReportParamError('No matching records found')
+        else:
+            ctx.push_page('report_' + rpt.render, rpt)
+
+
+def report_export(ctx, reportparams):
+    # Consistency check (form versions)
+    msgs = reportparams.check()
+    ctx.add_messages(msgs)
+    if msgs.have_errors():
+        # XXX Uh oh - warnings go into a black hole?
+        return
+    try:
+        row_gen = reportparams.export_rows(ctx.locals._credentials)
+    except reports.ReportParamError, e:
+        ctx.add_error(e)
+    else:
+        filename = datetime.now().strftime(config.appname + '-%Y%m%d-%H%M.csv')
+        page_common.csv_download(ctx, row_gen, filename)
diff --git a/pages/report_orderby.html b/pages/report_orderby.html
new file mode 100644
index 0000000..0bf84b1
--- /dev/null
+++ b/pages/report_orderby.html
@@ -0,0 +1,46 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<table>
+ <al-if expr="reportparams.order_by">
+  <al-for iter="o" expr="reportparams.order_by">
+   <tr>
+    <td><al-select 
+      nameexpr="'reportparams.order_by[%s].col' % o.index()"
+      optionexpr="reportparams.order_cols()" /></td>
+    <td><al-select 
+      nameexpr="'reportparams.order_by[%s].rev' % o.index()"
+      optionexpr="reportparams.rev_options" /></td>
+    <td align="right"><al-input type="submit" class="butt"
+       nameexpr="'del_order:%s' % o.index()" value="Delete" /></td>
+   </tr>
+  </al-for>
+ <al-else>
+  <tr>
+   <td colspan="3" align="center"><b>No ordering defined</b></td>
+  </tr>
+ </al-if>
+ <tr>
+  <td colspan="2"></td>
+  <td align="right">
+   <al-input class="butt" type="submit" name="add_order" value="Add" />
+  </td>
+ </tr>
+</table>
diff --git a/pages/report_table.html b/pages/report_table.html
new file mode 100644
index 0000000..43d7716
--- /dev/null
+++ b/pages/report_table.html
@@ -0,0 +1,82 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-if expr="confirm">
+ <al-expand name="page_layout_banner">
+  <al-setarg name="title"><al-value expr="reportgen.params.title(_credentials)"></al-setarg>
+  <al-expand name="confirm" />
+ </al-expand>
+<al-else>
+ <al-expand name="print_layout">
+  <al-setarg name="title"><al-value expr="reportgen.params.title(_credentials)"></al-setarg>
+  <al-setarg name="extrabuttons">
+   <al-input type="submit" class="backbutt" name="refresh" value="Refresh" />
+  </al-setarg>
+  <div class="linereport">
+   <h1 class="header"><al-value expr="reportgen.params.title(_credentials)" /></h1>
+   <al-value expr="len(reportgen)"> record<al-if expr="len(reportgen) != 1">s</al-if>.
+   <al-if expr="reportgen.params.preamble">
+    <p class="preamble">
+     <al-value expr="wiki_text(reportgen.params.expand(_credentials, 'preamble'))" noescape />
+    </p>
+   </al-if>
+   <al-expand name="confidential" />
+   <table id="linereport" class="clicktab">
+    <thead>
+     <tr>
+      <al-for iter="h" expr="reportgen.headings()">
+       <th><al-value expr="h.value()" /></th>
+      </al-for>
+     </tr>
+    </thead>
+    <tbody>
+     <al-for vars="gencase" iter="r" expr="reportgen">
+      <al-tr idexpr="'key:%s' % gencase.id" classexpr="page_common.alternate(r.index())">
+       <al-if expr="gencase.columns is not None">
+        <al-for iter="c" expr="gencase.columns">
+         <td>
+          <al-if expr="c.value() is not None">
+           <al-value expr="c.value()" />
+          </al-if>
+         </td>
+        </al-for>
+       </al-if>
+      </al-tr>
+      <al-for vars="key, label, text" iter="free_i" expr="gencase.freetext">
+       <al-tr idexpr="'key:%s' % key">
+        <al-td colspanexpr="reportgen.n_cols" class="free">
+         <al-if expr="label"><b><al-value expr="label" />: </b></al-if>
+         <al-value expr="text" />
+        </al-td>
+       </al-tr>
+      </al-for>
+     </al-for>
+    </tbody>
+   </table>
+   <al-if expr="reportgen.params.footer">
+    <p class="footer">
+     <al-value expr="wiki_text(reportgen.params.expand(_credentials, 'footer'))" noescape />
+    </p>
+   </al-if>
+  </div>
+  <al-input type="hidden" name="key" value="" />
+  <script>clicktab('linereport', 'appform');</script>
+ </al-expand>
+</al-if>
diff --git a/pages/report_table.py b/pages/report_table.py
new file mode 100644
index 0000000..49f1f00
--- /dev/null
+++ b/pages/report_table.py
@@ -0,0 +1,60 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import datetime
+from casemgr import globals, caseaccess
+from pages import page_common, report_ops
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_key(self, ctx, key):
+        page_common.go_id(ctx, key)
+
+    def do_refresh(self, ctx, ignore):
+        ctx.pop_page()
+        report_ops.run_report(ctx, ctx.locals.reportgen.params)
+
+page_process = PageOps().page_process
+
+
+class ConfirmReport(page_common.Confirm):
+    mode = 'confirm'
+    title = 'Large report'
+    buttons = [
+        ('back', 'No'),
+        ('continue', 'Yes'),
+    ]
+
+    def button_back(self, pageops, ctx):
+        ctx.pop_page()
+
+
+def page_enter(ctx, reportgen):
+    ctx.locals.reportgen = reportgen
+    ctx.add_session_vars('reportgen')
+    if len(ctx.locals.reportgen) > 5000:
+        raise ConfirmReport(message='This report has %s records and may take '
+                            'a considerable time to run. Do you wish to '
+                            'continue?' % len(ctx.locals.reportgen))
+
+def page_leave(ctx):
+    ctx.del_session_vars('reportgen')
+
+def page_display(ctx):
+    ctx.run_template('report_table.html')
diff --git a/pages/result.html b/pages/result.html
new file mode 100644
index 0000000..4438b83
--- /dev/null
+++ b/pages/result.html
@@ -0,0 +1,108 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="result_buttons">
+       <td class="buttcol" align="right">
+        <al-if expr="name">
+         <al-input type="submit" class="butt" 
+          expr="label" nameexpr="name" idexp="name" />
+        <al-elif expr="label">
+         <al-value expr="label" />
+        <al-else>
+          
+        </al-if>
+       </td>
+</al-macro>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">
+  <al-value expr="search.search_ops.title" />
+ </al-setarg>
+ <al-expand name="confirm_or_error">
+  <al-exec expr="result_page = search.result.result_page()" />
+  <div class="right">Select: <al-input type="submit" class="smallbutt" name="selectnone" value="None" /><al-input type="submit" class="smallbutt" name="selectall" value="All" /></div>
+  <al-if expr="search.result.description">
+   <div class="pagesubtitle"><al-value expr="search.result.description" /></div>
+  </al-if>
+  <div class="searchinfo">Search took 
+  <al-value expr="'%.2f' % search.result.search_time" /> seconds and
+  returned <al-value expr="search.result.result_count()" /> persons (<al-value
+  expr="search.result.pages()" /> pages). This page took 
+  <al-value expr="'%.2f' % search.result.page_time" /> seconds.</div>
+  <table width="100%" border="0" class="searchres" cellspacing="0">
+   <thead>
+    <al-exec expr="name, label = search.result.edit_button()" />
+    <al-if expr="name">
+     <td></td>
+     <al-expand name="result_buttons" />
+     <td></td>
+    </al-if>
+    <tr><td colspan="3"><al-expand name="page_select" /></td></tr>
+   </thead>
+   <tfoot>
+    <tr><td colspan="3"><al-expand name="page_select" /></td></tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="person_i" expr="result_page">
+     <al-exec expr="_person = person_i.value()" />
+     <tr class="person">
+      <td colspan="2">
+       <al-value expr="_person.summary()" />
+      </td>
+      <al-exec expr="name, label = _person.button(case)" />
+      <al-expand name="result_buttons" />
+     </tr>
+     <al-for iter="case_i" expr="_person.cases">
+      <al-exec expr="_case = case_i.value()" />
+      <al-if expr="_case.case_row.deleted"><tr class="deletedcase"><al-else><tr class="case"></al-if>
+       <td class="caseinfo">
+        <al-value expr="_case.summary()" />
+       </td>
+       <al-exec expr="name, label = _case.button(case)" />
+       <td class="select">
+        <al-if expr="name">
+         <al-input type="checkbox" list name="search.result.page_selected" 
+          valueexpr="_case.case_id" />
+        </al-if>
+       </td>
+       <al-expand name="result_buttons" />
+      </tr>
+     </al-for>
+    </al-for>
+   </tbody>
+  </table>
+
+  <table width="100%">
+   <tr>
+    <td colspan="3" class="fauxrule"></td>
+   </tr>
+   <tr>
+    <td align="left"></td>
+    <td align="right">
+     <al-exec expr="name, label = search.result.new_button()" />
+     <al-if expr="name">
+      <al-input type="submit" class="bigbutt" 
+       expr="label" nameexpr="name" idexp="name" />
+     </al-if>
+    </td>
+   </tr>
+  </table>
+ </al-expand>
+</al-expand>
diff --git a/pages/result.py b/pages/result.py
new file mode 100644
index 0000000..1500f33
--- /dev/null
+++ b/pages/result.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, paged_search
+from pages import page_common
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_search_op(self, ctx, op, *a):
+        ctx.locals.search.search_ops.do(ctx, self.confirmed, op, *a)
+
+    def do_selectnone(self, ctx, ignore):
+        ctx.locals.search.result.select([])
+        
+    def do_selectall(self, ctx, ignore):
+        ctx.locals.search.result.select_all()
+        
+
+pageops = PageOps()
+
+def page_enter(ctx):
+    paged_search.push_pager(ctx, ctx.locals.search.result)
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+
+def page_display(ctx):
+    ctx.run_template('result.html')
+
+def page_process(ctx):
+    ctx.locals.caseset = None
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/search.html b/pages/search.html
new file mode 100644
index 0000000..742e1ad
--- /dev/null
+++ b/pages/search.html
@@ -0,0 +1,154 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="search.search_ops.title" /></al-setarg>
+
+ <al-expand name="confirm_or_error">
+
+  <al-if expr="search.search_ops.show_quick">
+   <div class="search">
+    <div class="flag">
+      Enter a
+      <al-value expr="search.field_label('surname')" /> and/or
+      <al-value expr="search.field_label('given_names')" />, or
+      <al-value expr="search.field_label('local_case_id')" />,
+      <al-value expr="search.field_label('case_id')" /> or Form ID:
+    </div>
+
+    <table class="labelform" width="100%">
+     <tr>
+      <td class="label">
+       <label for="quicksearch">Quick search</label>
+      </td>
+      <td>
+       <al-input style="width: 99%;" name="search.quicksearch" id="quicksearch" entersubmit />
+      </td>
+     </tr>
+    </table>
+   </div>
+  </al-if>
+
+  <al-if expr="search.search_ops.show_caseset">
+   <div class="search">
+    <div class="flag"><al-if expr="search.search_ops.show_quick">OR s<al-else>S</al-if>elect a case set:</div>
+
+    <table class="pickset" width="100%">
+     <tr>
+      <th>Saved:</th>
+      <th>Recent:</th>
+     </tr>
+     <tr>
+      <td class="pspane">
+       <al-if expr="casesets.saved_casesets">
+        <table width="100%">
+         <al-for vars="csi" expr="casesets.saved_casesets">
+          <tr>
+           <td width="100%"><al-value expr="csi.name" /></td>
+           <td align="right">
+            <al-input type="submit" nameexpr="'cs_saved:%s' % csi.caseset_id" 
+               class="smallbutt" value=">>" /><td>
+          </tr>
+         </al-for>
+        </table>
+       <al-else>
+        <div>None</div>
+       </al-if>
+      </td>
+      <td class="pspane">
+       <al-if expr="casesets.recent_casesets">
+        <table width="100%">
+         <al-for vars="cs" iter="cs_i" expr="casesets.recent_casesets">
+          <tr>
+           <td width="100%"><al-value expr="cs.name" /></td>
+           <td align="right">
+            <al-input type="submit" nameexpr="'cs_recent:%s' % cs_i.index()" 
+               class="smallbutt" value=">>" /><td>
+          </tr>
+         </al-for>
+        </table>
+       <al-else>
+        <div>None</div>
+       </al-if>
+      </td>
+     </tr>
+    </table>
+   </div>
+  </al-if>
+
+  <div class="search">
+    <div class="flag"><al-value expr="search.search_ops.pretext" /></div>
+
+   <al-exec expr="render_fields = search.get_demog_fields()" />
+   <al-expand name="demogfields" />
+  </div>
+
+  <div class="search">
+   <table class="labelform options" width="100%">
+    <tr><td colspan="4" class="fauxrule"></td></tr>
+    <tr>
+     <td colspan="2">
+      <al-input type="checkbox" name="search.fuzzy" id="fuzzy" value="True" />
+       <label for="fuzzy">Phonetic Search</label>
+     </td>
+     <td class="label"><label for="order_by">Order results by</label></td>
+     <td class="field"><al-select name="search.order_by" id="order_by"
+          optionexpr="search.get_order_options()" /></td>
+    </tr>
+    <tr>
+     <td colspan="2"></td>
+     <td></td>
+     <td>
+      <al-input type="checkbox" name="search.reverse" id="reverse"
+       value="True" /> <label for="reverse">Reverse</label></td>
+    </tr>
+    <tr>
+     <td colspan="2"></td>
+     <td class="label" valign="baseline">
+      <label for="results_per_page">Results per page</label></td>
+     <td>
+      <span id="results_per_page">
+       <al-for iter="i" expr="search.persons_per_page_options">
+        <al-input type="radio" name="search.persons_per_page"
+          valueexpr="i.value()" /> <al-value expr="i.value()" /> 
+       </al-for>
+      </span>
+     </td>
+    </tr>
+   </table>
+
+   <table class="flag" width="100%" cellspacing="0" cellpadding="0">
+    <tr>
+     <td width="33%" align="left"></td>
+     <td width="34%" align="center">
+      <al-input type="submit" name="search_reset" value="Clear" class="butt" />
+     </td>
+     <td width="33%" align="right">
+      <al-input type="submit" name="search_go" value="Search >>" class="butt" />
+     </td>
+    </tr>
+   </table>
+  </div>
+  <script>enterSubmit('appform', 'search_go');</script>
+
+ </al-expand>
+</al-expand>
diff --git a/pages/search.py b/pages/search.py
new file mode 100644
index 0000000..e298eb7
--- /dev/null
+++ b/pages/search.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_search_reset(self, ctx, ignore):
+        ctx.locals.search.reset()
+
+    def do_search_go(self, ctx, ignore):
+        prefs = ctx.locals._credentials.prefs
+        ctx.locals.search.search(ctx.locals)
+        ctx.locals.search.search_ops.result(ctx, ctx.locals.search.result)
+
+    def do_cs_recent(self, ctx, idx):
+        cs = ctx.locals.casesets.recent_casesets[int(idx)]
+        ctx.locals.search.search_ops.result_caseset(ctx, self.confirmed, cs)
+
+    def do_cs_saved(self, ctx, id):
+        cs = ctx.locals.casesets.load(int(id))
+        ctx.locals.search.search_ops.result_caseset(ctx, self.confirmed, cs)
+
+    def do_tab(self, ctx, tab):
+        ctx.locals.search.tabs.select(tab)
+
+    def do_tagbrowse(self, ctx, ignore):
+        ctx.push_page('tagbrowse', 'Search tags', 'search.tags')
+
+pageops = PageOps()
+
+
+def page_enter(ctx, search_ops):
+    # Implement a stack of searches
+    s = search.Search(search_ops)
+    s.saved = ctx.locals.search
+    ctx.locals.search = s
+
+def page_leave(ctx):
+    if hasattr(ctx.locals, 'search'):
+        # Logout clears ctx.locals
+        ctx.locals.search = ctx.locals.search.saved
+
+def page_display(ctx):
+    ctx.locals.casesets.saved_casesets.refresh()
+    ctx.run_template('search.html')
+
+def page_process(ctx):
+    ctx.locals.search.save_prefs()
+    pageops.page_process(ctx)
diff --git a/pages/search_ops.py b/pages/search_ops.py
new file mode 100644
index 0000000..ef7171a
--- /dev/null
+++ b/pages/search_ops.py
@@ -0,0 +1,269 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Metadata and methods to support the search / new case / edit case /
+result pages
+"""
+
+from cocklebur import utils
+from casemgr import globals, syndrome, cases
+
+from pages import page_common, caseset_ops
+
+import config
+
+__metaclass__ = type
+
+class SO_Base:
+
+    show_quick = False
+    show_caseset = False
+    all_syndrome_result = False
+    saved = None
+    title = None
+    pretext = ''
+    context = 'search'
+
+    def __init__(self, cred, syndrome_id=None):
+        self.cred = cred
+        self.syndrome_id = syndrome_id
+        self.syndrome_name = None
+        if self.syndrome_id is not None:
+            self.syndrome_name = syndrome.syndromes[syndrome_id].name
+        self.prefs = cred.prefs
+        self.view_only = 'VIEWONLY' in cred.rights
+        if self.title:
+            self.title = self.title % dict(syndrome=self.syndrome_name)
+
+    def result(self, ctx, result):
+        if not result:
+            description = None
+            if result is not None:
+                description = getattr(result, 'description', None)
+            return self.result_none(ctx, description)
+        if result.result_type == 'form':
+            return self.result_form(ctx, result.summary_id)
+        if result.error:
+            return ctx.add_error(result.error)
+        one_case = result.single_case()
+        if hasattr(self, 'result_one') and one_case:
+            self.result_one(ctx, one_case)
+        else:
+            self.result_many(ctx)
+
+    def result_none(self, ctx, description):
+        """ 
+        Called when a search returns no results
+        """
+        if description is not None:
+            ctx.msg('warn', 'Nothing found for %s' % description)
+        else:
+            ctx.msg('warn', 'Nothing found')
+
+    def result_many(self, ctx):
+        """
+        Called when a search returns more than one case, or one case
+        and the SearchOps instance has no result_one implementation.
+        """
+        ctx.push_page('result')
+
+    def result_form(self, ctx, summary_id):
+        """
+        Called when a search matches a single form
+        """
+        ctx.msg('warn', 'Cannot use a form ID here')
+
+    def result_caseset(self, ctx, confirmed, cs):
+        pass
+
+    def button_new(self):
+        """
+        Returns result footer button label and action (eg "New Person")
+        """
+        return None, None
+
+    def button_edit(self):
+        """
+        Returns result header button label and action (unused?)
+        """
+        return None, None
+
+    def button_case(self, id, syndrome_id):
+        """
+        Returns result per-case button label and action (eg "Edit Case")
+        """
+        return None, None
+
+    def button_person(self, id):
+        """
+        Returns result per-person button label and action (eg "New Case")
+        """
+        return None, None
+
+    def do(self, ctx, confirmed, op, *a):
+        """
+        Dispatch button actions (as defined above) to searchops methods
+        """
+        getattr(self, 'do_' + op)(ctx, confirmed, *a)
+
+    def do_case(self, ctx, confirmed, case_id):
+        pass
+
+
+class SO_QuickSearch(SO_Base):
+
+    show_quick = True
+    show_caseset = True
+
+    def result_one(self, ctx, case_id):
+        ctx.locals.caseset = None
+        case = cases.edit_case(ctx.locals._credentials, case_id)
+        page_common.edit_case(ctx, case, push=True)
+
+    def result_form(self, ctx, summary_id):
+        case, ef = cases.edit_form(ctx.locals._credentials, summary_id)
+        ctx.locals.caseset = None
+        page_common.edit_case(ctx, case, push=True)
+        ctx.push_page('caseform', ef)
+
+    def result_caseset(self, ctx, confirmed, cs):
+        caseset_ops.use_caseset(ctx, cs)
+
+
+class SO_ResultEdit(SO_Base):
+
+    pretext = ('OR enter some details of the case(s) you would like to edit '
+               'or view:')
+
+    def button_case(self, id, syndrome_id):
+        if self.syndrome_id is not None and self.syndrome_id != syndrome_id:
+            return None, None
+        if self.view_only:
+            label = 'View Case'
+        else:
+            label = 'Edit Case'
+        return 'search_op:case:%s' % id, label
+
+    def do_case(self, ctx, confirmed, case_id):
+        selected = ctx.locals.search.result.get_selected()
+        if selected:
+            caseset_ops.make_caseset(ctx, selected,
+                                     ctx.locals.search.result.description)
+        else:
+            ctx.locals.caseset = None
+            case = cases.edit_case(ctx.locals._credentials, int(case_id))
+            page_common.edit_case(ctx, case, push=True)
+
+
+class SO_ResultNewCase(SO_Base):
+
+    def button_person(self, id):
+        if not self.view_only:
+            return 'search_op:person_new_case:%s' % id, 'New Case'
+        return None, None
+
+    def do_person_new_case(self, ctx, confirmed, person_id):
+        ctx.locals.caseset = None
+        case = page_common.new_case(ctx, self.syndrome_id,
+                                    from_search=ctx.locals.search,
+                                    use_person_id=int(person_id))
+        page_common.edit_case(ctx, case)
+
+
+class SO_ResultNewPerson(SO_ResultNewCase):
+
+    all_syndrome_result = True
+
+    def button_new(self):
+        return 'search_op:new', 'New ' + config.person_label
+
+    def do_new(self, ctx, confirmed):
+        ctx.locals.caseset = None
+        case = page_common.new_case(ctx, self.syndrome_id,
+                                    from_search=ctx.locals.search)
+        page_common.edit_case(ctx, case)
+
+    def result_none(self, ctx, description):
+        ctx.locals.caseset = None
+        case = page_common.new_case(ctx, self.syndrome_id,
+                                    from_search=ctx.locals.search)
+        page_common.edit_case(ctx, case)
+
+
+class SearchOps(SO_QuickSearch,SO_ResultEdit,SO_Base):
+
+    """
+    Home page "search" button. Syndrome is not fixed, no ability to
+    add new cases or persons.
+    """
+    title = 'Search'
+    pretext = ('OR enter some details of the case(s) you would like to edit '
+               'or view:')
+
+
+class EditOps(SO_ResultNewCase,SO_QuickSearch,SO_ResultEdit,SO_Base):
+    """
+    Per-syndrome "edit case" button. Syndrome is fixed, so we can add new
+    cases, but not new persons.
+    """
+    title = 'Edit case of: %(syndrome)s'
+
+
+class NewCaseOps(SO_ResultNewPerson,SO_ResultEdit,SO_Base):
+    """
+    Per-syndrome "new case" button. Can add new persons, and different context
+    demog fields are shown.
+    """
+
+    title = 'Add a case of: %(syndrome)s'
+    pretext = ('Please search now to make sure the %s you are going to add is '
+               'not already in the database:') % config.person_label.lower()
+    context = 'person'
+
+
+class AssocContactOps(SO_Base):
+
+    pretext = ('Search for records to associate with this case')
+    show_quick = True
+    show_caseset = True
+
+    def __init__(self, cred, syndrome_id, case, contacts):
+        super(AssocContactOps, self).__init__(cred, syndrome_id)
+        self.case_id = case.case_row.case_id
+        self.contacts = contacts
+        self.title = 'Associate records with %s' % case
+
+    def button_case(self, id, syndrome_id):
+        if self.contacts.is_contact(id):
+            return None, 'already associated'
+        else:
+            return 'search_op:assoc:%s' % id, 'Assoc'
+
+    def do_assoc(self, ctx, confirmed, id):
+        selected = ctx.locals.search.result.get_selected()
+        if selected:
+            ctx.push_page('casecontacts_assoc', 'Add', selected)
+        else:
+            self.result_one(ctx, id)
+
+    def result_one(self, ctx, id):
+        ctx.push_page('casecontacts_assoc', 'Add', [int(id)])
+
+    def result_caseset(self, ctx, confirmed, cs):
+        ctx.push_page('casecontacts_assoc', 'Add', cs.case_ids)
diff --git a/pages/search_pt.html b/pages/search_pt.html
new file mode 100644
index 0000000..151f50d
--- /dev/null
+++ b/pages/search_pt.html
@@ -0,0 +1,114 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="search_pt">
+  <table class="pt-search">
+   <tr class="title">
+    <th><al-usearg name="left_title" /></th>
+    <th><al-usearg name="right_title" /></th>
+    <td>
+     <al-input type="submit" class="butt" value="Clear"
+      nameexpr="'%s:clear' % pt_search.name" />
+    </td>
+   </tr>
+   <tr>
+    <td></td>
+    <td>
+     <al-input type="text" style="width:98%;"
+      nameexpr="'%s.search_term' % pt_search.name" />
+    </td>
+    <td>
+     <al-input type="submit" class="butt" value="Search"
+         nameexpr="'%s:search' % pt_search.name" />
+    </td>
+   </tr>
+   <tr>
+    <td class="pane">
+     <table>
+      <al-for iter="s" expr="pt_search.get_included()">
+       <tr>
+        <al-if expr="pt_search.is_ordered">
+         <td width="14">
+          <al-if expr="s.index() != 0">
+           <al-input type="image" height="10" width="14" alt="up"
+             srcexpr="appath('images/button-up.png')"
+             nameexpr="'%s:move_up:%s' % (pt_search.name, s.value()[0])" />
+          <al-else>
+           <al-img expr="appath('images/button-nil.png')"
+            height="10" width="14" alt="" />
+          </al-if>
+         </td>
+         <td width="14">
+          <al-if expr="s.index() < len(pt_search) - 1">
+           <al-input type="image" height="10" width="14" alt="dn"
+             srcexpr="appath('images/button-down.png')"
+             nameexpr="'%s:move_dn:%s' % (pt_search.name, s.value()[0])" />
+          <al-else>
+           <al-img expr="appath('images/button-nil.png')"
+            height="10" width="14" alt="" />
+          </al-if>
+         </td>
+        <al-else>
+         <td width="24"></td>
+        </al-if>
+        <td width="90%">
+         <al-value expr="s.value()[1]" />
+        </td>
+        <td width="5%">
+         <al-input type="image" 
+          srcexpr="appath('images/arrow-r.png')" height="15" width="24"
+          nameexpr="'%s:remove:%s' % (pt_search.name, s.value()[0])" />
+        </td>
+       </tr>
+      </al-for>
+     </table>
+    </td>
+    <td class="pane" colspan="2">
+     <al-if expr="pt_search.search_error">
+      <div class="reverr" align="center">
+       <al-value expr="pt_search.search_error" />
+      </div>
+     <al-elif expr="pt_search.get_available()">
+      <table>
+       <al-for iter="s" expr="pt_search.get_available()">
+        <tr>
+         <td width="5%" nowrap>
+          <al-input type="image" 
+           srcexpr="appath('images/arrow-l.png')" height="15" width="24"
+           nameexpr="'%s:add:%s' % (pt_search.name, s.value()[0])" />
+         </td>
+         <td>
+          <al-value expr="s.value()[1]" />
+         </td>
+         <td>
+          <al-if expr="pt_search.info_page">
+           <al-input type="image" 
+            srcexpr="appath('images/info.png')" height="24" width="24"
+            nameexpr="'%s:info:%s' % (pt_search.name, s.value()[0])" />
+          </al-if>
+         </td>
+        </tr>
+       </al-for>
+      </table>
+     </al-if>
+    </td>
+   </tr>
+  </table>
+</al-macro>
diff --git a/pages/selmergecase.html b/pages/selmergecase.html
new file mode 100644
index 0000000..5b36d5c
--- /dev/null
+++ b/pages/selmergecase.html
@@ -0,0 +1,98 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge case records</al-setarg>
+ <al-exec expr="field_rows = selcasemerge.personfields_rows_and_cols()" />
+ <al-expand name="demogfields_text" />
+
+ <al-if expr="not selcasemerge.syndsets">
+  <p class="err">No other records for this person</p>
+ <al-else>
+  <p>This person has multiple records. If you wish to merge
+   records, select A and B records from the same 
+   <al-value expr="config.syndrome_label" /> and then press <b>View</b>.</p>
+  <table class="duperes">
+   <thead>
+    <th style="text-align: center;">A</th>
+    <th style="text-align: center;">B</th>
+    <th>Deleted?</th>
+    <al-for iter="f_i" expr="casefields">
+     <th><al-value expr="f_i.value().label" /></th>
+    </al-for>
+   </thead>
+
+   <tfoot>
+    <tr>
+     <al-td colspanexpr="len(casefields) + 3">
+      <table width="100%">
+       <tr>
+        <td align="left">
+         <al-input name="back" type="submit" class="butt" value="Finished" />
+        </td>
+        <td align="right">
+         <al-input name="showmerge" type="submit" class="butt" value="View" />
+        </td>
+       </tr>
+      </table>
+     </al-td>
+    </tr>
+   </tfoot>
+
+   <tbody>
+    <al-for iter="syndset_i" expr="selcasemerge.syndsets">
+     <al-exec expr="syndset = syndset_i.value()" />
+     <tr>
+      <al-th colspanexpr="len(casefields) + 3" style="text-align: center;">
+       <al-value expr="syndset.name" />
+      </al-th>
+     </tr>
+     <al-for vars="mc" iter="case_i" expr="syndset">
+      <al-if expr="case_i.index() & 1"><tr class="darker"><al-else><tr></al-if>
+       <al-exec expr="value = '%d,%d' % (syndset_i.index(), case_i.index())" />
+       <td align="center">
+        <al-input name="selcasemerge.index_a" type="radio" valueexpr="value" />
+       </td>
+       <td align="center">
+        <al-input name="selcasemerge.index_b" type="radio" valueexpr="value" />
+       </td>
+       <al-if expr="mc.deleted">
+        <td class="warn">
+         DELETED<br>
+         <al-value expr="mc.delete_timestamp"><br>
+         <al-if expr="mc.delete_reason">
+          <al-value expr="mc.delete_reason"><br>
+         </al-if>
+        </td>
+       <al-else>
+        <td></td>
+       </al-if>
+       <al-for vars="field" expr="casefields">
+        <td><al-value expr="field.outtrans(mc)" /></td>
+       </al-for>
+      </tr>
+     </al-for>
+    </al-for>
+   </tbody>
+  </table>
+ </al-if>
+</al-expand>
diff --git a/pages/selmergecase.py b/pages/selmergecase.py
new file mode 100644
index 0000000..961fde1
--- /dev/null
+++ b/pages/selmergecase.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import casemerge
+
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_showmerge(self, ctx, ignore):
+        casemerge = ctx.locals.selcasemerge.get_casemerge()
+        ctx.push_page('mergecase', casemerge)
+
+pageops = PageOps()
+
+
+def page_enter(ctx, selcasemerge):
+    ctx.locals.selcasemerge = selcasemerge
+    ctx.add_session_vars('selcasemerge')
+
+def page_leave(ctx):
+    ctx.del_session_vars('selcasemerge')
+
+def page_display(ctx):
+    ctx.locals.selcasemerge.update()
+    ctx.locals.casefields = ctx.locals.selcasemerge.casefields()
+    ctx.run_template('selmergecase.html')
+    
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/selmergeforms.html b/pages/selmergeforms.html
new file mode 100644
index 0000000..a9c6840
--- /dev/null
+++ b/pages/selmergeforms.html
@@ -0,0 +1,99 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Merge forms</al-setarg>
+ <al-exec expr="field_rows = formmerge_record.rows_and_cols('form')" />
+ <al-if expr="formmerge_record.deleted"><div class="deleted"><al-else><div></al-if>
+  <al-expand name="demogfields_text" />
+ </div>
+
+ <p>Select an A and B form of the same form type and then select
+ <b>View</b> to see the proposed merge details.</p>
+ <table class="duperes">
+  <thead>
+   <th style="text-align: center;">A</th>
+   <th style="text-align: center;">B</th>
+   <th>Date</th>
+   <th>Summary</th>
+  </thead>
+
+  <tfoot>
+   <tr>
+    <td colspan="4">
+     <table width="100%">
+      <tr>
+       <td align="left">
+        <al-input name="back" type="submit" class="butt" value="Finished" />
+       </td>
+       <td align="right">
+        <al-input name="showmerge" type="submit" class="butt" value="View" />
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </tfoot>
+
+  <tbody>
+   <al-for iter="form_i" expr="formmerge_record.forms">
+    <al-exec expr="form = form_i.value()" />
+    <al-if expr="len(form.summaries) > 1">
+     <tr class="darker">
+      <td colspan="4">
+       <b><al-value expr="form.name" /></b>
+      </td>
+     </tr>
+     <al-for iter="summary_i" expr="form.summaries">
+      <al-exec expr="summary = summary_i.value()" />
+      <al-if expr="form.allow_multiple"><tr><al-else><tr class="warn"></al-if>
+       <td align="center">
+        <al-input name="key_a" type="radio" valueexpr="summary.summary_id" />
+       </td>
+       <td align="center">
+        <al-input name="key_b" type="radio" valueexpr="summary.summary_id" />
+       </td>
+       <td nowrap="nowrap" align="center">
+        <al-if expr="summary.deleted">
+         DELETED<br>
+         <al-if expr="summary.delete_reason">
+          <al-value expr="summary.delete_reason" /><br>
+         </al-if>
+        </al-if>
+        <al-value expr="form_ui.form_id(summary.summary_id)" /><br>
+        <al-value expr="summary.form_date.date()" /><br>
+        <al-value expr="summary.form_date.time()" /><br>
+       </td>
+       <al-if expr="summary.deleted">
+        <td width="100%" class="gray">
+       <al-else>
+        <td width="100%">
+       </al-if>
+        <al-value expr="wiki_oneliner(summary.summary)" noescape="noescape" />
+       </td>
+      </tr>
+     </al-for>
+    </al-if>
+   </al-for>
+  </tbody>
+ </table>
+</al-expand>
diff --git a/pages/selmergeforms.py b/pages/selmergeforms.py
new file mode 100644
index 0000000..6b0c1e0
--- /dev/null
+++ b/pages/selmergeforms.py
@@ -0,0 +1,52 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import form_ui
+from casemgr import formmerge
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_showmerge(self, ctx, ignore):
+        if not ctx.locals.key_a or not ctx.locals.key_b:
+            raise page_common.PageError('Select an A and B form')
+        key_a = int(ctx.locals.key_a)
+        key_b = int(ctx.locals.key_b)
+        if key_a == key_b:
+            raise page_common.PageError('Select separate A and B forms')
+        case_id = ctx.locals.formmerge_record.case_row.case_id
+        merge = formmerge.FormMerge(case_id, key_a, key_b)
+        ctx.push_page('mergeform', merge)
+
+pageops = PageOps()
+
+def page_enter(ctx, formmerge_record):
+    ctx.locals.formmerge_record = formmerge_record
+    ctx.add_session_vars('formmerge_record')
+
+def page_leave(ctx):
+    ctx.del_session_vars('formmerge_record')
+
+def page_display(ctx):
+    ctx.run_template('selmergeforms.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
+
diff --git a/pages/selprintforms.html b/pages/selprintforms.html
new file mode 100644
index 0000000..d208c8f
--- /dev/null
+++ b/pages/selprintforms.html
@@ -0,0 +1,68 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Print <al-value expr="syndromes[forms.syndrome_id].name" /> Forms</al-setarg>
+  <table border="0" class="selexp">
+   <al-if expr="forms.forms">
+    <tr>
+     <td width="30%">
+      Which forms would you like to print?
+     </td>
+     <td>
+      <table>
+       <al-for iter="form_i" expr="forms.forms">
+        <al-exec expr="form = form_i.value()">
+        <tr>
+         <td>
+          <al-input type="checkbox" name="forms.include_forms" 
+           valueexpr="form.label" list />
+         </td>
+         <td width="100%">
+          <al-value expr="form.name" />
+         </td>
+        </tr>
+       </al-for>
+      </table>
+     </td>
+     <td align="center">
+      <al-if expr="len(forms.forms) > 0">
+       <al-input type="submit" name="select_all" 
+        class="bigbutt" value="Select All" />
+       <br />
+       <al-input type="submit" name="clear_all" 
+        class="bigbutt" value="Clear Selection" />
+      </al-if>
+     </td>
+    </tr>
+   </al-if>
+   <tr>
+    <td colspan="3" class="fauxrule"></td>
+   </tr>
+   <tr>
+    <td align="left">
+    </td>
+    <td></td>
+    <td align="center">
+      <al-input type="submit" name="okay" class="butt" value="Okay" />
+    </td>
+   </tr>
+  </table>
+</al-expand>
diff --git a/pages/selprintforms.py b/pages/selprintforms.py
new file mode 100644
index 0000000..1fa9420
--- /dev/null
+++ b/pages/selprintforms.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+from cocklebur import dbobj
+from casemgr import globals, printforms
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def page_process(self, ctx):
+        ctx.locals.forms.refresh()
+        page_common.PageOpsBase.page_process(self, ctx)
+
+    def do_select_all(self, ctx, ignore):
+        ctx.locals.forms.select_all()
+
+    def do_clear_all(self, ctx, ignore):
+        ctx.locals.forms.clear()
+
+    def do_okay(self, ctx, ignore):
+        ctx.locals.forms.check_forms()
+        ctx.push_page('printforms')
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.forms = printforms.Forms(syndrome_id)
+    ctx.add_session_vars('forms')
+
+def page_leave(ctx):
+    ctx.del_session_vars('forms')
+
+def page_display(ctx):
+    ctx.run_template('selprintforms.html')
diff --git a/pages/shutdown.html b/pages/shutdown.html
new file mode 100644
index 0000000..9532b72
--- /dev/null
+++ b/pages/shutdown.html
@@ -0,0 +1,29 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="error_layout.html" />
+<al-expand name="error_layout">
+ <al-setarg name="title">Application is not available</al-setarg>
+
+ <p>This application is currently unavailable because the session
+ server is not running. Please try again later or contact 
+ <al-value expr="helpdesk_contact" />.</p>
+ <p><input type="submit" name="retry" value="Try again"></p>
+</al-expand>
diff --git a/pages/syn_detail.html b/pages/syn_detail.html
new file mode 100644
index 0000000..b07d0a2
--- /dev/null
+++ b/pages/syn_detail.html
@@ -0,0 +1,33 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="config.syndrome_label"> for <al-value expr="syndrome.name"></al-setarg>
+
+ <div>
+  <p><al-value expr="wiki_text(syndrome.description)" noescape="noescape"/></p>
+  <div class="date">
+   <al-if expr="syndrome.post_date">
+    Posted <al-value expr="syndrome.post_date">
+   </al-if>
+  </div>
+  <p><al-value expr="wiki_text(syndrome.additional_info)" noescape="noescape"/></p>
+ </div>
+</al-expand>
diff --git a/pages/syn_detail.py b/pages/syn_detail.py
new file mode 100644
index 0000000..5af334a
--- /dev/null
+++ b/pages/syn_detail.py
@@ -0,0 +1,38 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import syndrome
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+    pass
+
+pageops = PageOps()
+
+def page_enter(ctx, syndrome_id):
+    ctx.locals.syndrome = syndrome.syndromes[syndrome_id].getrow()
+    ctx.add_session_vars('syndrome')
+
+def page_leave(ctx):
+    ctx.del_session_vars('syndrome')
+
+def page_display(ctx):
+    ctx.run_template('syn_detail.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/syndlist.html b/pages/syndlist.html
new file mode 100644
index 0000000..aa7f98e
--- /dev/null
+++ b/pages/syndlist.html
@@ -0,0 +1,92 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="syndlist">
+  <div class="syndlist">
+   <al-if expr="synd_list">
+    <al-input name="syn_detail" type="hidden" value="" />
+    <al-for vars="item" iter="syndrome_i" expr="synd_list">
+     <div class="syndrome">
+      <al-div class="foldicon" 
+        idexpr="'icon_syndrome_%s' % item.syndrome_id"></al-div>
+      <al-if expr="item.syndrome.has_additional_info">
+        <al-input class="info" type="image"
+          nameexpr="'syn_detail:%s' % item.syndrome_id" 
+          srcexpr="appath('images/info.png')" />
+      </al-if>
+      <al-h1 idexpr="'label_syndrome_%s' % item.syndrome_id">
+        <al-value expr="item.syndrome.name" />
+      </al-h1>
+      <div class="actions">
+       <al-for vars="mb_item" expr="item.menubar.left">
+        <al-div class="sl-action" idexpr="'label_'+mb_item.name">
+         <al-if expr="'butt' in mb_item.style">
+          <al-input class="sublink" type="submit"
+           nameexpr="mb_item.name" expr="mb_item.label" />
+          <al-if expr="mb_item.drop">
+           <al-span class="sla-more clickable" 
+            idexpr="'icon_'+mb_item.name">▼</al-span>
+          <al-else>
+             
+          </al-if>
+         <al-elif expr="mb_item.drop">
+          <al-span class="clickable sublink" idexpr="'icon_'+mb_item.name">
+           <al-value expr="mb_item.label" />
+           <span class="sla-more">▼</span>
+          </al-span>
+         </al-if>
+         <al-if expr="mb_item.drop">
+          <al-div class="mb_items" idexpr="'list_' + mb_item.name">
+           <al-for vars="name, label" expr="mb_item.drop">
+            <al-div class="clickable" idexpr="name"><al-value expr="label" /></al-div>
+            <al-comment><al-input class="sublink" type="submit"
+             nameexpr="name" expr="label" /><br></al-comment>
+           </al-for>
+          </al-div>
+          <script>droplist('<al-value expr="mb_item.name" />', true);</script>
+         </al-if>
+        </al-div>
+       </al-for>
+      </div>
+      <al-if expr="item.syndrome.description">
+       <al-div class="detail" 
+         idexpr="'fold_syndrome_%s' % item.syndrome_id">
+         <al-value expr="wiki_text(item.syndrome.description)" 
+          noescape="noescape"/>
+         <script>linkfold('syndrome_<al-value expr="item.syndrome_id" />', true);</script>
+       </al-div>
+      </al-if>
+      <div class="count">
+       <al-if expr="item.record_count is not None">
+        <al-value expr="item.record_count"> records
+       </al-if>
+      </div>
+      <al-if expr="item.syndrome.post_date">
+       <div class="count">
+        Posted <al-value expr="item.syndrome.post_date">
+       </div>
+      </al-if>
+     </div>
+    </al-for>
+   <al-else>
+    No <al-value expr="config.syndrome_label.lower()" />s available
+   </al-if>
+  </div>
+</al-macro>
diff --git a/pages/tabs.html b/pages/tabs.html
new file mode 100644
index 0000000..4a31bcf
--- /dev/null
+++ b/pages/tabs.html
@@ -0,0 +1,59 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="left-tabs">
+ <al-if expr="tabs">
+  <table class="ltab">
+   <tr>
+    <al-td class="tabs" styleexpr="tabs.css_width()">
+     <al-for iter="tab_i" expr="tabs">
+      <al-exec expr="tab = tab_i.value()" />
+      <al-if expr="not tab.spacer">
+       <al-input type="submit" disabledbool="tab.selected()" 
+        accesskeyexpr="tab.accesskey" classexpr="tab.css_class()"
+        nameexpr="tab.nameexpr" expr="tab.label" />
+      <al-else>
+       <br>
+      </al-if>
+     </al-for>
+    </al-td>
+    <td class="tabcontent">
+     <al-usearg />
+    </td>
+   </tr>
+  </table>
+ </al-if>
+</al-macro>
+
+<al-macro name="top-tabs">
+ <al-if expr="tabs">
+  <div class="ttabs">
+   <al-for iter="tab_i" expr="tabs">
+    <al-exec expr="tab = tab_i.value()" />
+    <al-input type="submit" disabledbool="tab.selected()" 
+     accesskeyexpr="tab.accesskey" classexpr="tab.css_class()"
+     nameexpr="tab.nameexpr" expr="tab.label" styleexpr="tabs.css_width()" />
+   </al-for>
+   <div class="tabcontent">
+    <al-usearg />
+   </div>
+  </div>
+ </al-if>
+</al-macro>
diff --git a/pages/tagbrowse.html b/pages/tagbrowse.html
new file mode 100644
index 0000000..b51670b
--- /dev/null
+++ b/pages/tagbrowse.html
@@ -0,0 +1,48 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="demogfields.html">
+<al-include name="taskbanner.html" />
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Tags</al-setarg>
+ <al-expand name="taskbanner" />
+ <al-if expr="case">
+  <al-exec expr="field_rows = case.rows_and_cols('form')" />
+  <al-if expr="case.deleted"><div class="deleted"><al-else><div></al-if>
+   <al-expand name="demogfields_text" />
+  </div>
+ </al-if>
+ <table class="tagbrowse">
+  <tr>
+   <th colspan="3"><al-value expr="tagbrowse_title" /></th>
+   <th><al-input type="submit" class="smallbutt" name="edit:" value="New"></th>
+  </tr>
+  <al-for vars="tag" expr="casetags.tags()">
+   <tr>
+    <td class="tag-checkbox">
+     <al-input type="checkbox" name="tags" valueexpr="tag.tag" list /></td>
+    <td class="tag-tag"><al-value expr="tag.tag" /></td>
+    <td class="tag-notes">
+     <al-if expr="tag.notes"><al-value expr="tag.notes" /></al-if></td>
+    <td><al-input type="submit" class="smallbutt" nameexpr="'edit:%s' % tag.tag_id" value="Edit"></td>
+   </tr>
+  </al-for>
+ </table>
+</al-expand>
diff --git a/pages/tagbrowse.py b/pages/tagbrowse.py
new file mode 100644
index 0000000..1e422af
--- /dev/null
+++ b/pages/tagbrowse.py
@@ -0,0 +1,56 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+
+from cocklebur import utils
+from casemgr import globals, casetags
+
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_back(self, ctx, ignore):
+        utils.nssetattr(ctx.locals, ctx.locals.targetfield, 
+                        ' '.join(ctx.locals.tags))
+        ctx.pop_page()
+
+    def do_edit(self, ctx, tag_id):
+        ctx.push_page('tagedit', tag_id)
+
+
+pageops = PageOps()
+
+def page_enter(ctx, tagbrowse_title, targetfield=None):
+    ctx.locals.tagbrowse_title = tagbrowse_title
+    ctx.locals.targetfield = targetfield
+    tags = utils.nsgetattr(ctx.locals, ctx.locals.targetfield)
+    ctx.locals.tags = list(casetags.tags_from_str(tags))
+    ctx.add_session_vars('tagbrowse_title', 'targetfield', 'tags')
+
+
+def page_leave(ctx):
+    ctx.del_session_vars('tagbrowse_title', 'targetfield', 'tags')
+
+def page_display(ctx):
+    ctx.run_template('tagbrowse.html')
+
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/tagedit.html b/pages/tagedit.html
new file mode 100644
index 0000000..3357a79
--- /dev/null
+++ b/pages/tagedit.html
@@ -0,0 +1,54 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Edit Tag</al-setarg>
+ <table class="widelabelform">
+  <tr>
+   <td class="label"><label for="tag.tag">Tag</label></td>
+   <td class="field"><al-input id="tag.tag" name="tag.tag" /></td>
+  </tr>
+  <tr>
+   <td class="label"><label for="tag.notes">Notes</label></td>
+   <td class="field">
+    <al-textarea id="tag.notes" name="tag.notes" cols="40" rows="6"/></td>
+  </tr>
+  <tr>
+   <td colspan="2" class="buttonbar">
+    <al-expand name="confirm_or_error">
+     <table>
+      <tr>
+       <td class="ll"></td>
+       <td class="mm">
+        <al-if expr="'ADMIN' in _credentials.rights">
+         <al-input type="submit" name="delete" class="danger butt" value="Delete" />
+        </al-if>
+       </td>
+       <td class="rr">
+        <al-input type="submit" name="okay" class="butt" value="Okay" />
+       </td>
+      </tr>
+     </table>
+    </al-expand>
+   </td>
+  </tr>
+ </table>
+</al-expand>
+
diff --git a/pages/tagedit.py b/pages/tagedit.py
new file mode 100644
index 0000000..f4da02a
--- /dev/null
+++ b/pages/tagedit.py
@@ -0,0 +1,68 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from pages import page_common
+
+from casemgr import globals, casetags
+
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_okay(self, ctx, ignore):
+        casetags.check_tag(ctx.locals.tag)
+        if ctx.locals.tag.is_new():
+            ctx.locals.tags.append(ctx.locals.tag.tag)
+        ctx.locals.tag.db_update(refetch=False)
+        globals.db.commit()
+        ctx.msg('info', 'Tag %r updated' % ctx.locals.tag.tag)
+        casetags.notify()
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        if not self.confirmed and not ctx.locals.tag.is_new():
+            case_count = casetags.use_count(ctx.locals.tag.tag_id)
+            if case_count:
+                raise page_common.ConfirmDelete(
+                    message='Deleting this tag will remove the tag from '
+                            '%s case(s). This can not be undone. Are you sure '
+                            'you wish to proceed?' % case_count)
+        if ctx.locals.tag.tag_id:
+            casetags.delete_tag(ctx.locals.tag.tag_id)
+        globals.db.commit()
+        ctx.msg('info', 'Tag %r deleted' % ctx.locals.tag.tag)
+        casetags.notify()
+        ctx.pop_page()
+
+
+pageops = PageOps()
+
+def page_enter(ctx, tag_id):
+    ctx.locals.tag = casetags.edit_tag(tag_id)
+    ctx.add_session_vars('tag')
+
+def page_leave(ctx):
+    ctx.del_session_vars('tag')
+
+def page_display(ctx):
+    ctx.run_template('tagedit.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
+
diff --git a/pages/taskaction.py b/pages/taskaction.py
new file mode 100644
index 0000000..2ac08d5
--- /dev/null
+++ b/pages/taskaction.py
@@ -0,0 +1,79 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Given a task_id, this code loads and locks the task, sets up preconditions
+for the action, and dispatches to the appropriate page.
+"""
+
+from cocklebur import form_ui
+from casemgr import globals, tasks, cases
+
+import page_common
+
+import config
+
+class TAError(Exception): pass
+
+def task_dispatch(ctx, task_id, set_page, edit=False):
+    cred = ctx.locals._credentials
+    if 'VIEWONLY' in cred.rights:
+        task = tasks.UnlockedTask(globals.db, cred, task_id)
+    else:
+        ctx.locals.task = task = tasks.LockedTask(globals.db, cred, task_id)
+    try:
+        case = ef = None
+        if task.action in tasks.action_req_case:
+            if task.case_id is None:
+                raise TAError('Case has been deleted')
+            case = ctx.locals.case
+            if case is None or case.case_row.case_id != task.case_id:
+                case = cases.edit_case(cred, task.case_id)
+                page_common.set_case(ctx, case)
+        if task.action in tasks.action_req_form_name:
+            if task.form_name is None:
+                raise TAError('Form definition has been deleted')
+            if task.action in tasks.action_req_summary_id:
+                if task.summary_id is None:
+                    raise TAError('Form instance has been deleted')
+                ef = case.edit_form(task.summary_id)
+            else:
+                form = case.getform(task.form_name)
+                if not form.allow_new_form() and form.summaries:
+                    raise TAError('Form has already been created')
+                ef = case.new_form(task.form_name)
+        if edit:
+            if task.action == tasks.ACTION_NOTE:
+                set_page('notetask', edit)
+            else:
+                set_page('casetask', case, True)
+        else:
+            if task.action == tasks.ACTION_NOTE:
+                set_page('notetask', edit)
+            elif task.action in tasks.action_case_edit:
+                set_page('case')
+            elif task.action in tasks.action_form_edit:
+                set_page('caseform', ef)
+            else:
+                raise TAError('This action is not implemented at this time')
+        globals.db.commit()
+    except (TAError, form_ui.FormError), e:
+        ctx.add_error(e)
+        task.annotation = '[%s]\n%s' % (e, task.annotation or '')
+        task.action == tasks.ACTION_NOTE
+        ta(ctx, task, set_page, edit=True)
diff --git a/pages/taskbanner.html b/pages/taskbanner.html
new file mode 100644
index 0000000..429665b
--- /dev/null
+++ b/pages/taskbanner.html
@@ -0,0 +1,35 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="taskbanner">
+ <al-if expr="task">
+  <div class="task">Task: <al-value expr="task.task_description" /> 
+   <al-if expr="task.was_locked">
+    <span class="danger">WARNING: in use by <al-value expr="task.was_locked" /></span>
+   </al-if>
+   <al-if expr="task.annotation">
+    - <span class="smaller"><al-value expr="task.annotation" /></span> 
+   </al-if>
+    (Task ID <al-value expr="task.task_id" />
+     <al-if expr="task.assigner is not None">, 
+     Assigned by: <al-value expr="task.assigner.username" /></al-if>)
+  </div>
+ </al-if>
+</al-macro>
diff --git a/pages/taskedit.html b/pages/taskedit.html
new file mode 100644
index 0000000..5667d3b
--- /dev/null
+++ b/pages/taskedit.html
@@ -0,0 +1,259 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-macro name="task_assign_search">
+    <table width="95%">
+     <tr>
+      <al-th colspanexpr="assign_search.span()">
+       <al-value expr="assign_search.title" /></al-th>
+     </tr>
+     <tr>
+      <al-td colspanexpr="assign_search.span()" width="100%">
+       <table width="100%" cellpadding="0" cellspacing="0">
+        <tr>
+         <td width="100%">
+          <al-input name="assign_search.term" class="fill" />
+         </td>
+         <td nowrap valign="middle">
+          <al-input class="butt" type="submit" name="as:search" value="Search" />
+          <al-input class="butt" type="submit" name="as:clear" value="Clear" />
+          <al-input class="butt" type="submit" name="cancel_search" value="Cancel" />
+         </td>
+        </tr>
+       </table>
+      </al-td>
+     </tr>
+     <al-if expr="assign_search.search_error">
+      <tr>
+       <al-td class="reverr" colspanexpr="assign_search.span()">
+        <al-value expr="assign_search.search_error" />
+       </al-td>
+      </tr>
+     </al-if>
+     <al-for iter="ri" expr="assign_search.result">
+      <al-exec expr="row = ri.value()" />
+      <tr>
+       <al-for iter="ci" expr="assign_search.showcols">
+        <td><al-value expr="getattr(row, ci.value())" /></td>
+       </al-for>
+       <al-if expr="assign_search.info_page">
+        <td>
+         <al-input type="image" 
+          srcexpr="appath('images/info.png')" height="24" width="24"
+          nameexpr="taskedit.row_select('search_info', row)" />
+        </td>
+       </al-if>
+       <td align="right">
+        <al-input class="butt" type="submit" value="Select"
+          nameexpr="taskedit.row_select('search_assign', row)" />
+       </td>
+      </tr>
+     </al-for>
+    </table>
+</al-macro>
+
+<al-macro name="task_edit">
+    <table width="95%" cellspacing="0" border="0">
+     <tr>
+      <th colspan="2" align="left">
+       <al-if expr="edittask.seed_task_id">
+        <al-if expr="edittask.inplace">
+         <al-if expr="edittask.action in tasks.action_closed">
+          Editing CLOSED task:
+         <al-else>
+          Editing task:
+         </al-if>
+        <al-else>
+         Next task:
+        </al-if>
+       <al-else>
+        Creating a new task:
+       </al-if>
+      </th>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td width="30%">
+       <label for="task_description">Task Description</label>
+      </td>
+      <td><al-input class="fill" id="task_description" name="edittask.task_description" /></td>
+     </tr>
+     <tr>
+      <td align="right">
+       or <al-input type="submit" name="apply_desc" class="bigbutt"
+        value="Apply Description" />
+      </td>
+      <td><al-select class="fill" id="task_descriptions" 
+        name="popular_tasks" optionexpr="task_options" />
+      </td>
+     </tr>
+     
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td><label for="annotation">Task Notes</label></td>
+      <td><al-textarea class="fill" id="annotation" name="edittask.annotation" rows="4" /></td>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <al-if expr="fh and fh.forms">
+      <tr>
+       <td><label for="form_name">Form for task</label></td>
+       <td><al-select class="fill" id="form_name" name="edittask.form_name" 
+         optionexpr="fh.forms" onchange="submit();"/><br>
+        <al-input type="submit" name="showforms" class="bigbutt"
+         value="Refresh form list" />
+       </td>
+      </tr>
+      <al-if expr="fh.form_instances is not None">
+       <tr>
+        <td><label for="active"><al-value expr="fh.selected_form" /> forms</label></td>
+        <td>
+         <table>
+          <al-for iter="fi" expr="fh.form_instances">
+          <al-exec expr="summary_id, summary_date, summary = fi.value()" />
+           <tr>
+            <td><al-input type="radio"
+               name="edittask.summary_id" valueexpr="summary_id" /></td>
+            <td><al-value expr="summary_date" /></td>
+            <td><al-value expr="wiki_oneliner(summary)" /></td>
+           </tr>
+          </al-for>
+         </table>
+        </td>
+       </tr>
+      </al-if>
+     </al-if>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td>Assign task to</td>
+      <td>
+       <table width="100%">
+        <al-for iter="at" expr="edittask.assignee.assign_types">
+         <al-exec expr="value, label = at.value()" />
+         <tr>
+          <td>
+           <al-input type="radio" name="edittask.assignee.assign_type" 
+            valueexpr="value" idexpr="'assign_' + value" /></td>
+          <td width="40%">
+           <al-label forexpr="'assign_' + value"><al-value expr="label" /></al-label>
+          </td>
+          <td width="40%">
+           <al-lookup expr="value">
+            <al-item expr="'originator'">
+             <al-value expr="edittask.assignee.originator_str()" />
+            </al-item>
+            <al-item expr="'assigner'">
+             <al-value expr="edittask.assignee.last_assigner_str()" />
+            </al-item>
+            <al-item expr="'queue'">
+             <al-select class="fill" name="edittask.assignee.queue_id"
+              optionexpr="edittask.assignee.workqueues.options()" />
+            </al-item>
+            <al-item expr="'unit'">
+             <al-value expr="edittask.assignee.unit_str()" />
+            </al-item>
+            <al-item expr="'user'">
+             <al-value expr="edittask.assignee.user_str()" />
+            </al-item>
+           </al-lookup>
+          </td>
+          <td align="right">
+           <al-lookup expr="value">
+            <al-item expr="'unit'">
+             <al-input type="submit" name="search_unit" 
+              class="bigbutt" expr="'Select ' + config.unit_label" />
+            </al-item>
+            <al-item expr="'user'">
+             <al-input type="submit" name="search_user" 
+              class="bigbutt" value="Select User" />
+            </al-item>
+           </al-lookup>
+          </td>
+         </tr>
+        </al-for>
+       </table>
+      </td>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td><label for="active">Start task within/by</label></td>
+      <td><al-select class="fill" id="active" name="edittask.active" 
+       optionexpr="edittask.active_options" /></td>
+     </tr>
+     <tr>
+      <td align="right">
+       <label for="active_abs">or enter a date</label>
+      </td>
+      <td>
+       <al-input id="active_abs" name="edittask.active_abs" size="30" 
+        calendarformatexpr="edittask.datetime_format" />
+      </td>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td><label for="due">Complete within/by</label></td>
+      <td><al-select class="fill" id="due" name="edittask.due" 
+       optionexpr="edittask.due_options" /></td>
+     </tr>
+     <tr>
+      <td align="right">
+       <label for="due_abs">or enter a date</label>
+      </td>
+      <td><al-input id="due_abs" name="edittask.due_abs" size="30" 
+       calendarformatexpr="edittask.datetime_format" /></td>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+     <tr>
+      <td><label for="repeat">Repeat task</label></td>
+      <td><al-select id="repeat" name="edittask.repeat" 
+       optionexpr="edittask.repeat_options" /> 
+       <al-select id="repeatcount" name="edittask.repeatcount" 
+        optionexpr="edittask.repeatcount_options" /> times.
+      </td>
+     </tr>
+
+     <tr><td colspan="2" class="fauxrule"></td></tr>
+
+    </table>
+</al-macro>
+
+<al-macro name="taskedit">
+ <div class="task">
+  <al-if expr="assign_search">
+   <al-expand name="task_assign_search" />
+  <al-else>
+   <al-expand name="task_edit" />
+   <al-expand name="menubar" />
+   <script>enterSubmit('appform', 'update');</script>
+  </al-if>
+ </div>
+</al-macro>
diff --git a/pages/taskedit.py b/pages/taskedit.py
new file mode 100644
index 0000000..3d234c8
--- /dev/null
+++ b/pages/taskedit.py
@@ -0,0 +1,170 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj, tablesearch, form_ui
+from casemgr import globals, tasks, cases, taskdesc
+from pages import page_common
+import config
+
+def assign_search(ctx, table, col, mode, **kwargs):
+    search = tablesearch.TableSearch(globals.db, table, col, **kwargs)
+    ctx.locals.assign_search = search
+    ctx.locals.edittask.assignee.assign_type = mode
+
+def row_select(op, row):
+    keys = [str(k) for k in row.get_keys()]
+    return ':'.join([op] + keys)
+
+class PageOps(page_common.PageOpsBase):
+    def rollback(self, ctx):
+        ctx.locals.edittask = None
+
+    def commit(self, ctx):
+        ctx.locals.edittask.update(globals.db)
+        globals.db.commit()
+        ctx.locals.task = None
+  
+    def msg(self, ctx, op):
+        ctx.add_message('%s %r task' % (op, 
+                        ctx.locals.edittask.task_description))
+
+    def do_close(self, ctx, ignore):
+        ctx.locals.edittask.close(globals.db)
+        globals.db.commit()
+        self.msg(ctx, 'Closed')
+        ctx.locals.task = None
+        ctx.pop_page()
+
+    def do_delete(self, ctx, ignore):
+        ctx.locals.edittask.delete(globals.db)
+        globals.db.commit()
+        self.msg(ctx, 'Deleted')
+        ctx.locals.task = None
+        ctx.pop_page()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        if ctx.locals.edittask.inplace:
+            self.msg(ctx, 'Updated')
+        else:
+            self.msg(ctx, 'Created')
+        ctx.pop_page()
+
+    def do_search_unit(self, ctx, ignore):
+        assign_search(ctx, 'units', 'name', 'unit',
+                      filter='enabled',
+                      title='Assign task to ' + config.unit_label,
+                      info_page=True)
+
+    def do_search_user(self, ctx, ignore):
+        assign_search(ctx, 'users', 'username', 'user',
+                      showcols=('username', 'fullname'),
+                      filter='enabled',
+                      title='Assign task to user')
+
+    def do_search_assign(self, ctx, id):
+        if ctx.locals.assign_search.table == 'units':
+            ctx.locals.edittask.assignee.unit_id = int(id)
+        else:
+            ctx.locals.edittask.assignee.user_id = int(id)
+        ctx.locals.assign_search = None
+
+    def do_search_info(self, ctx, id):
+        ctx.push_page('unitview', id)
+
+    def do_as(self, ctx, op, *args):
+        ctx.locals.assign_search.do(op, *args)
+
+    def do_cancel_search(self, ctx, ignore):
+        ctx.locals.assign_search = None
+
+    def do_apply_desc(self, ctx, ignore):
+        params = taskdesc.params(ctx.locals.task_synd_id, 
+                                 ctx.locals.popular_tasks)
+        if config.debug:
+            p = ['%s=%s' % kv for kv in params.items()]
+            ctx.log('task params: %s' % ', '.join(p))
+        ctx.locals.edittask.set_params(globals.db, **params)
+
+
+class FormsHelper:
+    def __init__(self, case, task):
+        self.forms = []
+        self.selected_form = None
+        self.form_instances = None
+        if case:
+            self.forms.append(('None', 'No form'))
+            for form in case.forms:
+                self.forms.append((form.label, form.name))
+            if task.form_name and task.form_name != 'None':
+                try:
+                    form = case.getform(task.form_name)
+                except form_ui.FormError, e:
+                    task.form_name = None
+                    return
+                self.selected_form = form.name
+                self.form_instances = []
+                if form.allow_new_form():
+                    self.form_instances.append((None, '', 'New form'))
+                for summary in form.summaries:
+                    self.form_instances.append((summary.summary_id, 
+                                                summary.form_date,
+                                                summary.summary))
+                for summary_id, form_date, summary in self.form_instances:
+                    if summary_id == task.summary_id:
+                        break
+                else:
+                    task.summary_id = self.form_instances[0][0]
+
+def get_menubar(ctx):
+    menubar = page_common.MenuBar()
+    if ctx.locals.edittask.seed_task_id:
+        if ctx.locals.edittask.inplace:
+            if ctx.locals.edittask.action in tasks.action_closed:
+                menubar.add_middle('close', 'Mark Completed')
+                menubar.add_right('update', 'Re-open task')
+            else:
+                menubar.add_middle('delete', 'Delete Task')
+                menubar.add_middle('close', 'Task Completed')
+                menubar.add_right('update', 'Update task')
+        else:
+            menubar.add_middle('close', 'Task Completed')
+            menubar.add_right('update', 'Create next task(s)')
+    else:
+        menubar.add_right('update', 'Create task')
+    menubar.done()
+    return menubar
+
+
+def page_enter(ctx, syndrome_id, edittask):
+    ctx.locals.task_synd_id = syndrome_id
+    ctx.locals.edittask = edittask
+    ctx.locals.assign_search = None
+    ctx.add_session_vars('task_synd_id', 'edittask', 'assign_search')
+
+def page_leave(ctx):
+    page_common.unlock_task(ctx)
+    ctx.del_session_vars('task_synd_id', 'edittask', 'assign_search') 
+
+def page_display(ctx):
+    ctx.locals.task_options = taskdesc.task_options(ctx.locals.task_synd_id)
+    ctx.locals.menubar = get_menubar(ctx)
+    ctx.locals.fh = None
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/tasks.html b/pages/tasks.html
new file mode 100644
index 0000000..dac2ca6
--- /dev/null
+++ b/pages/tasks.html
@@ -0,0 +1,144 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">
+  <al-if expr="paged_search.case_id is None">
+   Tasks
+  <al-else>
+   Tasks for Case System ID <al-value expr="paged_search.case_id" />
+  </al-if>
+ </al-setarg>
+ <al-exec expr="result_page = paged_search.result_page(_credentials)" />
+ <span class="smaller">Search took 
+ <al-value expr="'%.2f' % paged_search.search_time" /> seconds and
+ returned <al-value expr="paged_search.result_count()" /> tasks (<al-value
+ expr="paged_search.pages()" /> pages). This page took 
+ <al-value expr="'%.2f' % paged_search.page_time" /> seconds.</span>
+ <div class="boxed">
+  <label for="view_type">View: </label>
+  <al-select id="view_type" name="paged_search.params.view_type" 
+   optionexpr="paged_search.user_views(_credentials)" onchange="submit();" /> 
+  <label for="order_by">Order by: </label>
+  <al-select id="order_by" name="paged_search.params.order_by" 
+   optionexpr="paged_search.orders" onchange="submit();" /> 
+  <span class="smaller">
+   <label for="include_completed">Include completed: </label>
+   <al-input id="include_completed" type="checkbox" onclick="submit();"
+    name="paged_search.params.include_completed" value="True" /> 
+   <label for="include_future">Include future: </label>
+   <al-input id="include_future" type="checkbox" onclick="submit();"  
+    name="paged_search.params.include_future" value="True" /> 
+   <label for="include_deleted_cases">Include deleted: </label>
+   <al-input id="include_deleted_cases" type="checkbox" onclick="submit();"
+    name="paged_search.params.include_deleted_cases" value="True" /> 
+  </span>
+  <al-if expr="not has_js">
+   <al-input type="submit" name="update" class="butt" value="Refresh" />
+  </al-if>
+ </div>
+ <al-if expr="paged_search.error">
+  <div class="reverr"><al-value expr="paged_search.error" /></div>
+ <al-else>
+  <table class="searchres" cellspacing="0" cellpadding="2">
+    <thead>
+     <tr><td colspan="11"><al-expand name="page_select" /></td></tr>
+     <tr>
+      <th>Task Id</th>
+      <th>Assignee</th>
+      <th>Start in/by</th>
+      <th>Complete in/by</th>
+      <th>Description</th>
+      <th>Notes</th>
+      <th>Case</th>
+      <th>Action</th>
+      <th>Originator/Assigner</th>
+      <th></th>
+     </tr>
+    </thead>
+    <tfoot>
+     <tr><td colspan="11"><al-expand name="page_select" /></td></tr>
+    </tfoot>
+    <tbody>
+     <al-for iter="task_i" expr="result_page">
+      <al-exec expr="rtask = task_i.value()" />
+      <al-if expr="task_i.index() & 1"><tr class="darker"><al-else><tr></al-if>
+       <td><al-value expr="rtask.task_id" /></td>
+       <td><al-value expr="rtask.assignee" /></td>
+       <al-td bgcolorexpr="rtask.active_color">
+        <al-value expr="rtask.active_relative" />
+       </al-td>
+       <al-td bgcolorexpr="rtask.complete_color">
+        <al-value expr="rtask.complete_relative" />
+       </al-td>
+       <td><al-value expr="rtask.task_description" /></td>
+       <td class="smaller">
+        <al-if expr="rtask.annotation">
+         <al-value expr="rtask.annotation" />
+        </al-if>
+       </td>
+       <td class="smaller">
+        <b>Case: </b><al-value expr="rtask.case_summary" />
+       </td>
+       <td><al-value expr="rtask.action_summary" /></td>
+       <td>
+        <al-if expr="rtask.originator and rtask.assigner and rtask.originator != rtask.assigner">
+         <al-value expr="rtask.originator.username" />/<br>
+        </al-if>
+        <al-if expr="rtask.assigner">
+         <al-value expr="rtask.assigner.username" /><br>
+         <al-value expr="rtask.assignment_date" />
+        </al-if>
+       </td>
+       <td align="right">
+        <al-if expr="paged_search.allow_edit">
+         <al-input type="submit" class="smallbutt" value="Edit" 
+          nameexpr="'edit:%s' % rtask.task_id" />
+        <al-else>
+         <al-if expr="rtask.action in tasks.action_dispatchable">
+          <al-if expr="paged_search.viewonly">
+           <al-input type="submit" class="smallbutt" value="View" 
+            nameexpr="'go:%s' % rtask.task_id" />
+          <al-else>
+           <al-if expr="rtask.completed_date">
+            <al-input type="submit" class="butt" value="Re-open" 
+             nameexpr="'go:%s' % rtask.task_id" />
+           <al-else>
+            <al-input type="submit" class="smallbutt" value="Go" 
+             nameexpr="'go:%s' % rtask.task_id" />
+           </al-if>
+          </al-if>
+         </al-if>
+        </al-if>
+        <al-if expr="rtask.locked_by">
+         <div class="danger smaller">
+          In use by
+          <al-value expr="rtask.locked_by.username" whitespace /> 
+          <al-value expr="rtask.locked_relative" />
+         </div>
+        </al-if>
+       </td>
+      </tr>
+     </al-for>
+    </tbody>
+  </table>
+ </al-if>
+</al-expand>
+
diff --git a/pages/tasks.py b/pages/tasks.py
new file mode 100644
index 0000000..d179f71
--- /dev/null
+++ b/pages/tasks.py
@@ -0,0 +1,66 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, tasksearch, paged_search, tasks
+from pages import page_common, taskaction
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_edit(self, ctx, task_id):
+        ctx.locals.paged_search.reset()
+        try:
+            taskaction.task_dispatch(ctx, int(task_id), ctx.push_page, True)
+        except taskaction.TAError, e:
+            ctx.add_error(e)
+
+    def do_go(self, ctx, task_id):
+        ctx.locals.paged_search.reset()
+        try:
+            taskaction.task_dispatch(ctx, int(task_id), ctx.push_page)
+        except taskaction.TAError, e:
+            ctx.add_error(e)
+
+    def do_note(self, ctx, ignore):
+        if 'TASKINIT' in ctx.locals._credentials.rights:
+            ctx.push_page('notetask')
+
+    def do_results_prev_page(self, ctx, ignore):
+        ctx.locals.paged_search.prev()
+
+    def do_results_next_page(self, ctx, ignore):
+        ctx.locals.paged_search.next()
+
+pageops = PageOps()
+
+
+def page_enter(ctx):
+    paged_search.push_pager(ctx, tasksearch.TaskSearch(globals.db, 
+                                          ctx.locals._credentials,
+                                          ctx.locals._credentials.prefs))
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+
+def page_display(ctx):
+    page_common.idle(ctx)
+    ctx.run_template('tasks.html')
+
+def page_process(ctx):
+    ctx.locals.paged_search.new_search()
+    pageops.page_process(ctx)
diff --git a/pages/tools.html b/pages/tools.html
new file mode 100644
index 0000000..9669cd9
--- /dev/null
+++ b/pages/tools.html
@@ -0,0 +1,103 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Tools</al-setarg>
+  <table class="widelabelform">
+   <al-if expr="'DATAMGR' in _credentials.rights">
+    <tr><th colspan="2">Data</th></tr>
+    <tr>
+     <td class="label">
+      <label for="dupecfg">Duplicate <al-value expr="config.person_label" /> record matching</label>
+     </td>
+     <td><al-input id="dupecfg" name="dupecfg" type="submit" class="butt" value=">>" /></td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="dupecases">Duplicate Case record matching</label>
+     </td>
+     <td><al-input id="dupecases" name="dupecases" type="submit" class="butt" value=">>" /></td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="manualdupe">Manually merge <al-value expr="config.person_label" /> records associated with Cases</label>
+     </td>
+     <td><al-input id="manualdupe" name="manualdupe" type="submit" class="butt" value=">>" /></td>
+    </tr>
+   </al-if>
+   <al-if expr="'IMPORT' in _credentials.rights">
+    <tr>
+     <td class="label">
+      <label for="conflicts">View import conflicts</label></td>
+     <td><al-input id="conflicts" name="conflicts" type="submit" class="butt" value=">>" /></td>
+    </tr>
+   </al-if>
+
+   <tr><th colspan="2">User</th></tr>
+   <tr>
+    <td class="label">
+     <label for="prefsedit">Preferences</label></td>
+    <td><al-input type="submit" class="butt" id="prefsedit" name="prefsedit" value=">>" /></td>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="details">Edit your details</label></td>
+    <td><al-input type="submit" class="butt" id="details" name="details" value=">>" /></td>
+   </tr>
+   <tr>
+    <td class="label">
+     <label for="viewlog">View a log of your activities</label></td>
+    <td><al-input type="submit" class="butt" id="viewlog" name="viewlog" value=">>" /></td>
+   </tr>
+   <al-if expr="config.user_browser">
+    <tr>
+     <td class="label">
+      <label for="user_browser">Browse user contact details</label></td>
+     <td><al-input type="submit" class="butt" id="user_browser" name="user_browser" value=">>" /></td>
+    </tr>
+   </al-if>
+   <al-if expr="can_sponsor(_credentials)">
+    <tr>
+     <td class="label">
+      <label for="user_sponsor">Sponsor a new user onto the system</label></td>
+     <td><al-input type="submit" class="butt" id="user_sponsor" name="user_sponsor" value=">>" /></td>
+    </tr>
+   </al-if>
+
+   <al-if expr="'UNITADMIN' in _credentials.rights or 'TQADMIN' in _credentials.rights">
+    <tr><th colspan="2">Administration</th></tr>
+   </al-if>
+   <al-if expr="'UNITADMIN' in _credentials.rights">
+    <tr>
+     <td class="label">
+      <label for="users">Administer <al-value expr="config.unit_label.lower()" /> users</label></td>
+     <td><al-input type="submit" class="butt" id="users" name="users" value=">>" /></td>
+    </tr>
+   </al-if>
+   <al-if expr="'TQADMIN' in _credentials.rights">
+    <tr>
+     <td class="label">
+      <label for="taskqueue">Administer task queues</label></td>
+     <td><al-input type="submit" class="butt" id="taskqueue" name="taskqueue" value=">>" /></td>
+    </tr>
+   </al-if>
+  </table>
+</al-expand>
+
diff --git a/pages/tools.py b/pages/tools.py
new file mode 100644
index 0000000..7a88609
--- /dev/null
+++ b/pages/tools.py
@@ -0,0 +1,87 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import logview, globals, tasks, paged_search, user_edit
+from casemgr import persondupecfg, persondupe
+from pages import page_common
+import config
+
+def can_sponsor(cred):
+    return (config.user_registration_mode in ('invite', 'sponsor')
+            and 'SPONSOR' in cred.rights)
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_manualdupe(self, ctx, ignore):
+        if 'DATAMGR' in ctx.locals._credentials.rights:
+            ctx.push_page('manualdupe')
+
+    def do_dupecfg(self, ctx, ignore):
+        if 'DATAMGR' in ctx.locals._credentials.rights:
+            ctx.push_page('dupepersons_config')
+        
+    def do_dupecases(self, ctx, ignore):
+        if 'DATAMGR' in ctx.locals._credentials.rights:
+            ctx.push_page('dupecases_config')
+
+    def do_conflicts(self, ctx, ignore):
+        if 'IMPORT' in ctx.locals._credentials.rights:
+            dp = persondupe.loadconflicts(globals.db)
+            ctx.push_page('dupepersons', dp, 'Import conflicts')
+
+    def do_users(self, ctx, ignore):
+        if 'UNITADMIN' in ctx.locals._credentials.rights:
+            ctx.push_page('useradmin')
+
+    def do_taskqueue(self, ctx, ignore):
+        if 'TQADMIN' in ctx.locals._credentials.rights:
+            prefs = ctx.locals._credentials.prefs
+            query = tasks.user_workqueues_query(ctx.locals._credentials,
+                                                order_by='name')
+            search = paged_search.SortablePagedSearch(globals.db, prefs, query)
+            ctx.push_page('user_queues', search)
+
+    def do_details(self, ctx, ignore):
+        ue = user_edit.EditSelf(ctx.locals._credentials)
+        ctx.push_page('useredit', ue)
+
+    def do_prefsedit(self, ctx, ignore):
+        ctx.push_page('prefsedit')
+
+    def do_viewlog(self, ctx, ignore):
+        creds = ctx.locals._credentials
+        log = logview.UserLogView(creds.prefs, 
+                                  'Log for user %r' % creds.user.username,
+                                  user_id=creds.user.user_id)
+        ctx.push_page('logview', log)
+
+    def do_user_browser(self, ctx, ignore):
+        if config.user_browser:
+            ctx.push_page('user_browser')
+
+    def do_user_sponsor(self, ctx, ignore):
+        if can_sponsor(ctx.locals._credentials):
+            ctx.push_page('user_sponsor')
+
+pageops = PageOps()
+
+
+def page_display(ctx):
+    ctx.run_template('tools.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/traceback.html b/pages/traceback.html
new file mode 100644
index 0000000..bb24ae5
--- /dev/null
+++ b/pages/traceback.html
@@ -0,0 +1,32 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="error_layout.html" />
+<al-expand name="error_layout">
+ <al-setarg name="title">Application Error</al-setarg>
+ <p>Sorry - an error has occurred in this application.</p>
+
+ <p>Details of the error have been forwarded to the system maintainers.</p>
+
+ <p>You will now be logged out of the application to clear the error. You
+  are welcome to log back in.</p>
+
+ <p><input type="submit" name="okay" value="Okay"></p>
+</al-expand>
diff --git a/pages/unitselect.py b/pages/unitselect.py
new file mode 100644
index 0000000..831e0e4
--- /dev/null
+++ b/pages/unitselect.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# NOTE - this page module is a sub-function of the "login" module, and shares
+# the login page template. It gets called on login if the user is a member of
+# multiple units, and has not already chosen a unit.
+
+from casemgr import globals
+from pages import page_common
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_login(self, ctx, ignore):
+        cred = ctx.locals._credentials
+        assert cred.user
+        if not ctx.locals.unit_id:
+            raise page_common.PageError('Please select a unit')
+        cred.set_unit(globals.db, int(ctx.locals.unit_id))
+        ctx.set_page('main')
+
+page_process = PageOps().page_process
+
+def page_enter(ctx):
+    ctx.locals.unit_options = list(ctx.locals._credentials.unit_options)
+    choose = ('', '(select a %s)' % config.unit_label.lower())
+    ctx.locals.unit_options.insert(0, choose)
+    ctx.locals.unit_id = ''
+    ctx.add_session_vars('unit_options', 'unit_id')
+
+def page_leave(ctx):
+    ctx.del_session_vars('unit_options', 'unit_id')
+
+def page_display(ctx):
+    ctx.locals.username = ctx.locals._credentials.user.username
+    ctx.run_template('login.html')
diff --git a/pages/unitview.html b/pages/unitview.html
new file mode 100644
index 0000000..43b2295
--- /dev/null
+++ b/pages/unitview.html
@@ -0,0 +1,49 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Unit View</al-setarg>
+ <table class="widelabelform">
+  <tr>
+   <td class="label">
+    <label>Unit name</label></td>
+   <td><al-value expr="unit.name" /></td>
+  </tr>
+  <tr>
+   <td class="label">
+    <label>Street Address</label></td>
+   <td><al-value expr="unit.street_address" /></td>
+  </tr>
+  <tr>
+   <td class="label">
+    <label>Postal Address</label></td>
+   <td><al-value expr="unit.postal_address" /></td>
+  </tr>
+  <tr>
+   <td>
+    <al-expand name="confirm_or_error" />
+   </td>
+  </tr>
+ </table>
+ <al-include name="user_search.html" />
+</al-expand>
+
diff --git a/pages/unitview.py b/pages/unitview.py
new file mode 100644
index 0000000..deee509
--- /dev/null
+++ b/pages/unitview.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from casemgr import globals, paged_search, user_search
+from pages import page_common
+
+import config
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_pager(self, ctx, op):
+        ctx.locals.unit_users.do(op)
+
+pageops = PageOps()
+
+def page_enter(ctx, unit_id):
+    cred = ctx.locals._credentials
+    query = globals.db.query('units')
+    query.where('unit_id = %s', unit_id)
+    ctx.locals.unit = query.fetchone()
+    unit_users = user_search.UserByUnitSearch(cred.prefs, unit_id)
+    paged_search.push_pager(ctx, unit_users)
+    ctx.add_session_vars('unit')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('unit')
+
+def page_display(ctx):
+    ctx.run_template('unitview.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
diff --git a/pages/user_browser.html b/pages/user_browser.html
new file mode 100644
index 0000000..bc05042
--- /dev/null
+++ b/pages/user_browser.html
@@ -0,0 +1,25 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="paged_search.title" /></al-setarg>
+ <al-input name="term" size="30"><al-input type="submit" name="okay" class="butt" value="Search" /><al-input type="submit" name="export" class="butt" value="Export" /><br>
+ <al-include name="user_search.html" />
+</al-expand>
diff --git a/pages/user_browser.py b/pages/user_browser.py
new file mode 100644
index 0000000..32982c2
--- /dev/null
+++ b/pages/user_browser.py
@@ -0,0 +1,48 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import user_search, paged_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_export(self, ctx, ignore):
+        page_common.csv_download(ctx, ctx.locals.paged_search.yield_rows(),
+                                 'users.csv')
+
+    def page_process(self, ctx):
+        if page_common.PageOpsBase.page_process(self, ctx):
+            return
+        if ctx.locals.term != ctx.locals.paged_search.term:
+            ctx.locals.paged_search.new_query(ctx.locals.term)
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx):
+    search = user_search.UserSearch(ctx.locals._credentials.prefs)
+    paged_search.push_pager(ctx, search)
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+
+def page_display(ctx):
+    if not page_common.send_download(ctx):
+        ctx.run_template('user_browser.html')
diff --git a/pages/user_queue.html b/pages/user_queue.html
new file mode 100644
index 0000000..92ebf81
--- /dev/null
+++ b/pages/user_queue.html
@@ -0,0 +1,99 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-include name="search_pt.html" />
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Task Queue <al-value expr="repr(queue.name)" /></al-setarg>
+  <table border="0" class="admin-u">
+   <tr>
+    <td colspan="3">
+     <table width="100%" cellpadding="0" cellspacing="0" border="0">
+      <tr>
+       <th width="100%">Edit queue</th>
+      </tr>
+     </table>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Task Queue Name:</td>
+    <td><al-input name="queue.name" size="40" /></td>
+    <td rowspan="2">
+     <al-if expr="queue_stats">
+      <div class="key">
+       Queue stats:
+       <table>
+        <al-for iter="qs" expr="queue_stats">
+         <al-exec expr="label, count = qs.value()" />
+         <tr>
+          <td><al-value expr="label" /></td>
+          <td><al-value expr="count" /></td>
+         </tr>
+        </al-for>
+       </table>
+      </div>
+     </al-if>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Description:</td>
+    <td><al-input name="queue.description" size="40" /></td>
+   </tr>
+   <tr>
+    <td align="right"><al-value expr="config.unit_label" />:</td>
+    <td colspan="2">
+     <al-exec expr="pt_search = unit_pt_search" />
+     <al-expand name="search_pt">
+      <al-setarg name="left_title">Selected</al-setarg>
+      <al-setarg name="left_row"><al-value expr="row.name" /></al-setarg>
+      <al-setarg name="right_title">Add/Search</al-setarg>
+      <al-setarg name="right_row"><al-value expr="row.name" /></al-setarg>
+     </al-expand>
+    </td>
+   </tr>
+   <tr>
+    <td align="right">Users:</td>
+    <td colspan="2">
+     <al-exec expr="pt_search = user_pt_search" />
+     <al-expand name="search_pt">
+      <al-setarg name="left_title">Selected</al-setarg>
+      <al-setarg name="left_row"><al-value expr="row.name" /></al-setarg>
+      <al-setarg name="right_title">Add/Search</al-setarg>
+      <al-setarg name="right_row"><al-value expr="row.name" /></al-setarg>
+     </al-expand>
+    </td>
+   </tr>
+
+   <tr>
+    <td colspan="3" class="darkest">
+     <al-expand name="confirm_or_error">
+      <table width="100%" cellpadding="0" cellspacing="0">
+       <tr>
+        <td align="right">
+         <al-input class="butt" name="update" type="submit" value="Save" />
+         <script>enterSubmit('appform', 'update');</script>
+        </td>
+       </tr>
+      </table>
+     </al-expand>
+    </td>
+   <tr>
+  </table>
+</al-expand>
diff --git a/pages/user_queue.py b/pages/user_queue.py
new file mode 100644
index 0000000..b4d2c40
--- /dev/null
+++ b/pages/user_queue.py
@@ -0,0 +1,28 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from pages import page_common, admin_queue
+import config
+
+# This page extracts most of it's functionality from the admin_queue page
+
+page_enter = admin_queue.page_enter
+page_leave = admin_queue.page_leave
+page_process = admin_queue.page_process
+
+def page_display(ctx):
+    ctx.run_template('user_queue.html')
diff --git a/pages/user_queues.html b/pages/user_queues.html
new file mode 100644
index 0000000..4219d44
--- /dev/null
+++ b/pages/user_queues.html
@@ -0,0 +1,63 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Task Queues</al-setarg>
+  <table border="0" class="admin-u">
+   <thead class="darkest">
+    <tr>
+     <al-expand name="sort_header" />
+     <th style="text-align: center;">
+      <al-input name="add:" class="butt" type="submit" value="New Queue" />
+     </th>
+    </tr>
+   </thead>
+   <tfoot class="darkest">
+    <al-if expr="paged_search.has_pages()">
+     <tr><td colspan="4"><al-expand name="page_select" /></td></tr>
+    </al-if>
+   </tfoot>
+   <tbody>
+    <al-for iter="queues_i" expr="paged_search.result_page()">
+     <al-exec expr="queue = queues_i.value()" />
+     <al-if expr="queues_i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+       <td><al-value expr="queue.name" /></td>
+       <td width="100%"><al-value expr="queue.description" /></td>
+       <td align="center" nowrap>
+        <al-input nameexpr="'edit:%s' % queue.queue_id" 
+             class="smallbutt" type="submit" value="Edit" />
+       </td>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <td colspan="4" class="reverr">
+       <al-value expr="paged_search.error" />
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+</al-expand>
+
diff --git a/pages/user_queues.py b/pages/user_queues.py
new file mode 100644
index 0000000..9d464aa
--- /dev/null
+++ b/pages/user_queues.py
@@ -0,0 +1,53 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, paged_search
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def do_add(self, ctx, ignore):
+        ctx.locals.queue_result.reset()
+        ctx.push_page('user_queue', None)
+
+    def do_edit(self, ctx, group_id):
+        ctx.push_page('user_queue', int(group_id))
+
+pageops = PageOps()
+
+
+def page_enter(ctx, queue_result):
+    queue_result.headers = [
+        ('name', 'Name'),
+        ('description', 'Description'),
+    ]
+    ctx.locals.queue_result = queue_result
+    paged_search.push_pager(ctx, ctx.locals.queue_result)
+    ctx.add_session_vars('queue_result')
+
+def page_leave(ctx):
+    paged_search.pop_pager(ctx)
+    ctx.del_session_vars('queue_result')
+
+def page_display(ctx):
+    ctx.run_template('user_queues.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/pages/user_search.html b/pages/user_search.html
new file mode 100644
index 0000000..6d373ca
--- /dev/null
+++ b/pages/user_search.html
@@ -0,0 +1,68 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+  <table border="0" class="gridtab">
+   <thead>
+    <tr>
+     <al-td colspanexpr="len(paged_search.headers)">
+      <al-expand name="page_select" />
+     </al-td>
+    </tr>
+    <tr>
+     <al-expand name="sort_header" />
+    </tr>
+   </thead>
+   <tfoot>
+    <tr>
+     <al-td colspanexpr="len(paged_search.headers)">
+      <al-expand name="page_select" />
+     </al-td>
+    </tr>
+   </tfoot>
+   <tbody>
+    <al-for iter="i" expr="paged_search.result_page()">
+     <al-exec expr="row = i.value()" />
+     <al-if expr="i.index() & 1">
+      <tr>
+     <al-else>
+      <tr class="darker">
+     </al-if>
+      <al-for iter="j" expr="row">
+       <td>
+        <al-if expr="isinstance(j.value(), list)">
+         <al-for iter="k" expr="j.value()">
+          <al-value expr="k.value()" /><br>
+         </al-for>
+        <al-else>
+         <al-value expr="j.value()" />
+        </al-if>
+       </td>
+      </al-for>
+     </tr>
+    </al-for>
+    <al-if expr="paged_search.error">
+     <tr>
+      <al-td colspanexpr="len(paged_search.headers)" class="reverr">
+       <al-value expr="paged_search.error" />
+      </al-td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
diff --git a/pages/user_sponsor.html b/pages/user_sponsor.html
new file mode 100644
index 0000000..046f237
--- /dev/null
+++ b/pages/user_sponsor.html
@@ -0,0 +1,101 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title">Sponsor a user</al-setarg>
+
+ <al-expand name="confirm_or_error">
+  <h3>Enter details of the prospective user you wish to invite to use the system:</h3>
+  <table class="widelabelform">
+   <tr>
+    <td class="label"><label for="fullname">Full name</label></td>
+    <td class="field">
+     <al-input name="fullname" id="fullname" />
+    </td>
+   </tr>
+   <tr>
+    <td class="label"><label for="email">e-mail address</label></td>
+    <td class="field">
+     <al-input name="email" id="email" />
+    </td>
+   </tr>
+   <tr>
+    <td colspan="2" align="right">
+     <al-input type="submit" class="butt" name="invite" value="Invite" />
+    </td>
+   </tr>
+  </table>
+
+  <al-if expr="pending_enable">
+   <h3>Accepted invitations pending approval:</h3>
+   <table class="gridtab">
+    <thead>
+     <tr>
+      <th>Full Name</th>
+      <th>Username</th>
+      <th>e-mail</th>
+      <th></th>
+    </thead>
+    <tbody>
+     <al-for vars="invite" expr="pending_enable">
+      <tr>
+       <td><al-value expr="invite.fullname" /></td>
+       <td><al-value expr="invite.username" /></td>
+       <td><al-value expr="invite.email" /></td>
+       <td><al-input type="submit" class="butt" value="Approve"
+           nameexpr="'enable:%s' % invite.user_id" /></td>
+      </tr>
+     </al-for>
+    </tbody>
+   </table>
+  </al-if>
+
+  <al-if expr="outstanding_invites">
+   <h3>Outstanding invitations:</h3>
+   <table class="gridtab">
+    <thead>
+     <tr>
+      <th></th>
+      <th>Full Name</th>
+      <th>e-mail</th>
+      <th>Registration key</th>
+      <th>Date sent</th>
+      <th></th>
+    </thead>
+    <tbody>
+     <al-for vars="invite" expr="outstanding_invites">
+      <tr>
+       <td><al-input type="submit" class="butt" value="Revoke"
+           nameexpr="'revoke:%s' % invite.user_id" /></td>
+       <td><al-value expr="invite.fullname" /></td>
+       <td><al-value expr="invite.email" /></td>
+       <td><al-value expr="invite.enable_key" /></td>
+       <td><al-value expr="invite.date" /></td>
+       <td><al-input type="submit" class="butt" value="Resend"
+           nameexpr="'resend:%s' % invite.user_id" /></td>
+      </tr>
+     </al-for>
+    </tbody>
+   </table>
+  <al-else>
+   <p>No outstanding invitations</p>
+  </al-if>
+ </al-expand>
+</al-expand>
diff --git a/pages/user_sponsor.py b/pages/user_sponsor.py
new file mode 100644
index 0000000..859cfe3
--- /dev/null
+++ b/pages/user_sponsor.py
@@ -0,0 +1,114 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cocklebur import datetime
+
+from casemgr import globals, user_edit, credentials, unituser, notify
+
+from pages import page_common
+
+import config
+
+class Invite:
+    cols = (
+        'user_id', 'username', 'fullname', 'email', 'enable_key',
+        'creation_timestamp',
+    )
+
+    def __init__(self, *args):
+        for col, value in zip(self.cols, args):
+            setattr(self, col, value)
+        self.date = datetime.mx_parse_datetime(self.creation_timestamp)
+
+def load_invites(ctx):
+    query = globals.db.query('users', order_by='email')
+    query.where('sponsoring_user_id = %s', ctx.locals._credentials.user.user_id)
+    query.where('(NOT enabled AND NOT deleted)')
+    if config.user_registration_mode == 'invite':
+        query.where('username is null')
+    ctx.locals.outstanding_invites = []
+    ctx.locals.pending_enable = []
+    for row in query.fetchcols(Invite.cols):
+        invite = Invite(*row)
+        if invite.username:
+            ctx.locals.pending_enable.append(invite)
+        else:
+            ctx.locals.outstanding_invites.append(invite)
+
+
+class RevokeConfirm(page_common.Confirm):
+    title = 'Revoke invitation'
+    buttons = [
+        ('continue', 'No'),
+        ('confirm', 'Yes'),
+    ]
+
+
+class PageOps(page_common.PageOpsBase):
+
+    def do_invite(self, ctx, ignore):
+        ue = user_edit.Sponsor(ctx.locals._credentials,
+                               fullname=ctx.locals.fullname,
+                               email=ctx.locals.email)
+        ctx.add_messages(ue.messages)
+        globals.db.commit()
+        notify.register_invite(ctx, ue.user)
+        ctx.locals.email = None
+        ctx.locals.fullname = None
+
+    def do_enable(self, ctx, user_id):
+        if config.user_registration_mode == 'sponsor':
+            ue = user_edit.SponsorEnable(ctx.locals._credentials, int(user_id))
+            ctx.push_page('useredit', ue)
+
+    def do_resend(self, ctx, user_id):
+        query = globals.db.query('users')
+        query.where('user_id = %s', int(user_id))
+        query.where('username IS NULL')
+        user = query.fetchone()
+        if user:
+            notify.register_invite(ctx, user)
+
+    def do_revoke(self, ctx, user_id):
+        if not self.confirmed:
+            raise RevokeConfirm(message='Are you sure you want to revoke the invitation for %s?' % unituser.users[int(user_id)].fullname)
+        fullname = credentials.revoke_invite(globals.db, int(user_id))
+        if fullname:
+            globals.db.commit()
+            ctx.msg('info', 'Revoked invitation for %s' % fullname)
+        else:
+            ctx.msg('warn', 'No invitation found')
+
+
+page_process = PageOps().page_process
+
+
+def page_enter(ctx):
+    ctx.locals.email = None
+    ctx.locals.fullname = None
+    ctx.add_session_vars('email', 'fullname')
+
+
+def page_leave(ctx):
+    ctx.del_session_vars('email', 'fullname')
+
+
+def page_display(ctx):
+    load_invites(ctx)
+    ctx.run_template('user_sponsor.html')
+
diff --git a/pages/useradmin.html b/pages/useradmin.html
new file mode 100644
index 0000000..1fa1a95
--- /dev/null
+++ b/pages/useradmin.html
@@ -0,0 +1,72 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="extrahead">
+  <al-expand name="req-sorttable" />
+ </al-setarg>
+ <al-setarg name="title">
+  Administer <al-value expr="config.unit_label" /> users
+ </al-setarg>
+ <table id="user_table" class="adminusers sorttable">
+  <thead>
+   <tr>
+    <th class="sortable">Enabled</th>
+    <th class="sortable">Bad Pass</th>
+    <th class="sortable">Username</th>
+    <th class="sortable defaultsort">Full name</th>
+    <th class="sortable">Title</th>
+    <th></th>
+   </tr>
+  </thead>
+  <tfoot>
+   <tr>
+    <td colspan="6">
+     <table width="100%" cellpadding="0" cellspacing="0">
+      <tr>
+       <td align="right">
+        <al-input name="new" type="submit" value="New User" class="butt" />
+       </td>
+      </tr>
+     </table>
+    </td>
+   </tr>
+  </tfoot>
+  <tbody>
+   <al-for iter="u_i" expr="users">
+    <al-exec expr="user = u_i.value()" />
+    <al-if expr="u_i.index() & 1"><tr class="darker"><al-else><tr></al-if>
+     <td><al-value expr="user.enabled" lookup="boolean" /></td>
+     <td><al-value expr="user.timelock_str" /></td>
+     <td><al-value expr="user.username" /></td>
+     <td><al-value expr="user.fullname" /></td>
+     <td><al-value expr="user.title" /></td>
+     <td nowrap>
+      <al-input nameexpr="'edit:%s' % user.user_id" 
+       value="Edit" type="submit" class="smallbutt" />
+      <al-input nameexpr="'log:%s' % user.user_id" 
+       value="Log" type="submit" class="smallbutt" />
+     </td>
+    </tr>
+   </al-for>
+  </tbody>
+ </table>
+ <al-input name="sortby" type="hidden" />
+</al-expand>
diff --git a/pages/useradmin.py b/pages/useradmin.py
new file mode 100644
index 0000000..c319e0f
--- /dev/null
+++ b/pages/useradmin.py
@@ -0,0 +1,73 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals, credentials, logview, user_edit
+from pages import page_common
+import config
+
+class User:
+    def __init__(self, dbrow):
+        self.user_id = dbrow.user_id;
+        self.enabled = dbrow.enabled;
+        self.username = dbrow.username;
+        self.fullname = dbrow.fullname;
+        self.title = dbrow.title;
+        self.timelock_str = credentials.timelock_remain_str(dbrow)
+
+
+class PageOps(page_common.PageOpsBase):
+    def do_edit(self, ctx, user_id):
+        ue = user_edit.RoleAdmin(ctx.locals._credentials, int(user_id))
+        ctx.push_page('useredit', ue)
+
+    def do_log(self, ctx, user_id):
+        query = globals.db.query('users')
+        query.where('user_id = %s', user_id)
+        username = query.fetchone().username
+        log = logview.UserLogView(ctx.locals._credentials.prefs, 
+                                  'Log for user %r' % username, 
+                                  user_id=user_id)
+
+        ctx.push_page('logview', log)
+
+    def do_back(self, ctx, ignore):
+        ctx.pop_page()
+
+    def do_new(self, ctx, ignore):
+        ue = user_edit.RoleAdmin(ctx.locals._credentials, None)
+        ctx.push_page('useredit', ue)
+
+pageops = PageOps()
+
+
+def page_display(ctx):
+    cred = ctx.locals._credentials
+    try:
+        query = globals.db.query('users', order_by='fullname')
+        query.join('LEFT JOIN unit_users USING (user_id)')
+        query.where('unit_id = %s', cred.unit.unit_id)
+        query.where('not deleted')
+        ctx.locals.users = [User(r) for r in query.fetchall()]
+    except dbobj.DatabaseError, e:
+        ctx.add_error(e)
+        ctx.locals.users = []
+    ctx.run_template('useradmin.html')
+
+def page_process(ctx):
+    pageops.page_process(ctx)
+
diff --git a/pages/useredit.html b/pages/useredit.html
new file mode 100644
index 0000000..849fe7f
--- /dev/null
+++ b/pages/useredit.html
@@ -0,0 +1,252 @@
+<al-comment>
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+</al-comment>
+
+<al-expand name="page_layout_banner">
+ <al-setarg name="title"><al-value expr="ue.title" /></al-setarg>
+ <al-if expr="not ue.need_key">
+  <div class="key">
+   <al-if expr="ue.passwd_setting">
+    <b>Choosing a good password</b>
+    <p>
+    You must choose a password which is known only to you and which is
+    hard to guess. It must be at least 8 characters long and must contain a
+    mixture of upper and lower case letters and numbers. It may also contain
+    punctuation. Upper case letters must not only be at the beginning of the
+    password and digits must not only be at the end of the password.
+    </p>
+    <p>
+    It is unwise to base your password on obvious words or sets of numbers,
+    such as your name, or your partner's or employer's name, or your birthday,
+    telephone number, car registration number, and so on.
+    </p>
+   </al-if>
+     
+   <p><span class="required">*</span> - required field.</p>
+   <p><span class="onerequired">*</span> - at least one field must be supplied.</p>
+  </div>
+  <al-if expr="ue.user.is_new()">
+   <i>Please click on the HELP link at the upper right of this page for
+    further instructions.</i>
+  </al-if>
+  <al-if expr="ue.unregistered and config.login_helpdesk_contact">
+   <i>For assistance, contact <al-value expr="config.login_helpdesk_contact"
+    noescape />.</i>
+  </al-if>
+  <table>
+   <al-if expr="ue.enabling">
+    <thead>
+     <tr>
+      <td colspan="2" class="warn-msg">
+       <p>
+       Please carefully check the bona fides of the user you are
+       sponsoring.
+       <p>
+       Verify that the <i>correct person</i> has responded to your invitation.
+       <p>
+       Do NOT rely on these details when making contact with the
+       user to verify their request - <i>if the invitation was intercepted,
+       the contact details may not be correct</i>.
+      </td>
+     </tr>
+    </thead>
+   </al-if>
+   <tfoot>
+    <tr>
+     <td colspan="2">
+      <al-expand name="confirm_or_error">
+       <table width="100%" cellpadding="0" cellspacing="0">
+        <tr>
+         <al-if expr="ue.check_details()">
+          <td align="left">
+           <al-input name="checked" type="submit" 
+            value="Details are correct" class="bigbutt" />
+          </td>
+         </al-if>
+         <td align="right"><al-input name="update" type="submit" class="butt"
+             expr="ue.submit_button" /></td>
+        </tr>
+       </table>
+      </al-expand>
+     </td>
+    </tr>
+   </tfoot>
+   <tbody class="widelabelform">
+    <tr>
+     <td class="label">
+      <label for="username">Username</label></td>
+     <td class="field">
+      <al-input name="ue.user.username" id="username" 
+        disabledbool="not ue.edit_username or ue.view_only" />
+      <al-if expr="ue.edit_username"><span class="required">*</span></al-if>
+     </td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="fullname">Full name</label></td>
+     <td class="field">
+      <al-input name="ue.user.fullname" id="fullname" 
+        disabledbool="ue.view_only" />
+      <span class="required">*</span>
+     </td>
+    </tr>
+    <al-if expr="ue.sponsor">
+     <tr>
+      <td class="label">
+       <label for="title">Sponsored by</label></td>
+      <td class="field">
+       <al-value expr="ue.sponsor.fullname" /> (<al-value expr="ue.sponsor.username" />)
+      </td>
+     </tr>
+    </al-if>
+    <tr>
+     <td class="label">
+      <label for="title">Job Title</label></td>
+     <td class="field"><al-input name="ue.user.title" id="title" 
+      disabledbool="ue.view_only" /></td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="agency">Agency/Employer</label></td>
+     <td class="field"><al-input name="ue.user.agency" id="agency" 
+      disabledbool="ue.view_only" /></td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="expertise">Expertise</label></td>
+     <td class="field"><al-input name="ue.user.expertise" id="expertise" 
+      disabledbool="ue.view_only" /></td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="email">e-mail address</label></td>
+     <td class="field">
+      <al-input name="ue.user.email" id="email" disabledbool="ue.view_only" />
+      <span class="onerequired">*</span>
+     </td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="phone_wk">Work Phone</label></td>
+     <td class="field">
+      <al-input name="ue.user.phone_work" id="phone_wk" 
+       disabledbool="ue.view_only" />
+      <span class="onerequired">*</span>
+     </td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="phone_mbl">Mobile Phone</label></td>
+     <td class="field">
+      <al-input name="ue.user.phone_mobile" id="phone_mbl" 
+       disabledbool="ue.view_only" />
+      <span class="onerequired">*</span>
+     </td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="phone_hm">Home phone</label></td>
+     <td class="field">
+      <al-input name="ue.user.phone_home" id="phone_hm" 
+       disabledbool="ue.view_only" />
+      <span class="onerequired">*</span>
+     </td>
+    </tr>
+    <tr>
+     <td class="label">
+      <label for="phone_fax">Fax</label></td>
+     <td class="field">
+      <al-input name="ue.user.phone_fax" id="phone_fax" 
+       disabledbool="ue.view_only" /></td>
+    </tr>
+    <al-if expr="ue.admin_edit">
+     <tr>
+      <td class="label">
+       <label for="enabled">Enabled</label></td>
+      <td>
+       <al-input id="user_enabled" name="ue.user.enabled" type="radio" value="True">Yes 
+       <al-input id="user_disabled" name="ue.user.enabled" type="radio" value="">No
+      </td>
+     </tr>
+     <tr>
+      <td class="label">
+       <label for="timelock">Locked due to bad password</label>
+      </td>
+      <al-if expr="ue.lock_remain()">
+       <td bgcolor="#ffcccc">
+         <al-input class="right butt" type="submit" name="reset_attempts" value="Reset" />
+         Yes - <al-value expr="ue.lock_remain()" /> remain
+       </td>
+      <al-else>
+       <td>No</td>
+      </al-if>
+     </tr>
+    </al-if>
+    <al-if expr="ue.passwd_setting">
+     <tr>
+      <td colspan="2">
+       <al-value expr="ue.passwd_setting_prompt" />
+      </td>
+     </tr>
+     <al-if expr="ue.passwd_setting_check_old">
+      <tr>
+       <td class="label">
+        <label for="oldpwd">Current Password</label></td>
+       <td>
+        <al-input type="password" name="ue.pwd.old" id="oldpwd" />
+        <span class="required">*</span>
+       </td>
+      </tr>
+     </al-if>
+     <tr>
+      <td class="label">
+       <label for="pwda">
+        <al-if expr="ue.passwd_setting_check_old">New </al-if>
+         Password</label></td>
+      <td>
+       <al-input type="password" name="ue.pwd.new_a" id="pwda" />
+       <span class="required">*</span>
+      </td>
+     </tr>
+     <tr>
+      <td class="label">
+       <label for="pwdb">Retype Password</label></td>
+      <td>
+       <al-input type="password" name="ue.pwd.new_b" id="pwdb" />
+       <span class="required">*</span>
+      </td>
+     </tr>
+    </al-if>
+   </tbody>
+  </table>
+  <script>enterSubmit('appform', 'update');</script>
+ <al-else>
+  <table class="widelabelform">
+   <tr>
+    <td class="label">
+     <label for="enable_key">Enter registration key</label></td>
+    <td class="field">
+     <al-input name="ue.enable_key" id="enable_key" />
+    <td align="right">
+     <al-input type="submit" name="key_submit" value="Okay" class="butt" /></td>
+   </tr>
+   <script>enterSubmit('appform', 'key_submit');</script>
+  </table>
+ </al-if>
+</al-expand>
diff --git a/pages/useredit.py b/pages/useredit.py
new file mode 100644
index 0000000..e0385b0
--- /dev/null
+++ b/pages/useredit.py
@@ -0,0 +1,68 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import dbobj
+from casemgr import globals
+from pages import page_common
+
+import config
+
+class PageOps(page_common.PageOpsBase):
+    def commit(self, ctx):
+        ctx.locals.ue.save()
+        ctx.add_messages(ctx.locals.ue.messages)
+        globals.db.commit()
+        globals.notify.notify('users', ctx.locals.ue.user.user_id)
+
+    def unsaved_check(self, ctx):
+        if ctx.locals.ue.has_changed():
+            raise page_common.ConfirmSave
+
+    def do_reset_attempts(self, ctx, ignore):
+        ctx.locals.ue.reset_attempts()
+
+    def do_checked(self, ctx, ignore):
+        ctx.locals.ue.mark_checked()
+        ctx.pop_page()
+
+    def do_update(self, ctx, ignore):
+        self.commit(ctx)
+        ctx.pop_page()
+
+    def do_key_submit(self, ctx, ignore):
+        ctx.locals.ue.load()
+        if not ctx.locals.ue.need_key:
+            ctx.msg('info', 'Key accepted - enter your details')
+        else:
+            ctx.msg('err', 'Enter a valid registration key')
+
+pageops = PageOps()
+
+
+def page_enter(ctx, ue):
+    ctx.locals.ue = ue
+    ctx.add_session_vars('ue')
+
+def page_leave(ctx):
+    ctx.del_session_vars('ue')
+
+def page_display(ctx):
+    ctx.run_template('useredit.html')
+
+def page_process(ctx):
+    if pageops.page_process(ctx):
+        return
diff --git a/sdist.py b/sdist.py
new file mode 100644
index 0000000..e8930c9
--- /dev/null
+++ b/sdist.py
@@ -0,0 +1,42 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# $Id: sdist.py 4432 2011-03-03 01:08:38Z andrewm $
+
+from distutils.core import setup
+import sys, os
+sys.argv.extend(['sdist', '--force-manifest'])
+thisdir = os.path.normpath(os.path.dirname(__file__))
+from casemgr.version import __version__
+
+svnrev = os.popen('svnversion %s' % thisdir).read().strip()
+f = open(os.path.join(thisdir, 'casemgr', 'svnrev.py'), 'w')
+f.write('__svnrev__ = %r\n' % svnrev)
+f.close()
+
+import sys
+sys.argv.extend(['sdist', '--force-manifest'])
+setup(name = "NetEpi-Collection",
+    version = __version__,
+    maintainer = "NSW Department of Health",
+    maintainer_email = "Tim CHURCHES <TCHUR at doh.health.nsw.gov.au>",
+    description = "Network-enabled tools for epidemiology and public health practice",
+    url = "http://netepi.org/",
+    license = 'Health Administration Corporation Open Source License Version 1.2',
+    packages = [],
+)
diff --git a/simpleinst/__init__.py b/simpleinst/__init__.py
new file mode 100644
index 0000000..25b7131
--- /dev/null
+++ b/simpleinst/__init__.py
@@ -0,0 +1,66 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import simpleinst.defaults
+import simpleinst.platform
+import simpleinst.config_register
+import simpleinst.install_files
+import simpleinst.pyinstaller
+import simpleinst.utils
+from simpleinst.filter import Filter
+from simpleinst.utils import secret, getpass, collect, rm_pyc
+from simpleinst.usergroup import user_lookup
+
+# Config priority:
+#    10. command line
+#    20. config.py
+#    30. install.py (includes any explicitly set config attributes)
+#    40. platform defaults (from simpleinst.platform)
+#    50. defaults (from simpleinst.defaults)
+# Note that we add these in reverse order, as later, more complex configs
+# can depend on earlier ones
+config = simpleinst.config_register.Config()
+config.source(50, simpleinst.defaults.Defaults())
+config.source(40, simpleinst.platform.get_platform())
+config.source_attrs(30)
+config.source_file(20, 'config')
+config.source_cmdline(10)
+
+import os
+joinpath = os.path.join
+basename = os.path.basename
+dirname = os.path.dirname
+abspath = os.path.abspath
+normpath = os.path.normpath
+del os
+
+from py_compile import compile as _py_compile
+def py_compile(fn):
+    _py_compile(fn, doraise=True)
+
+def make_dirs(*args, **kwargs):
+    kwargs['config'] = config
+    return simpleinst.utils.make_dirs(*args, **kwargs)
+
+def install(**kwargs):
+    return simpleinst.install_files.install(config=config, **kwargs)
+
+def on_install(*args, **kwargs):
+    simpleinst.install_files.on_install(config, *args, **kwargs)
+
+def py_installer(name, *args, **kwargs):
+    return simpleinst.pyinstaller.py_installer(config, name, *args, **kwargs)
+
+python_bang_path_filter = Filter(config, pattern = '^#!.*',
+                                 subst = '#!%(python)s', count = 1)
+
+copy = simpleinst.install_files.copy
diff --git a/simpleinst/config_register.py b/simpleinst/config_register.py
new file mode 100644
index 0000000..8c70b64
--- /dev/null
+++ b/simpleinst/config_register.py
@@ -0,0 +1,185 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+
+import sys
+import os
+import imp
+import tempfile
+import bisect
+import fnmatch
+from simpleinst.utils import chown, chmod, normjoin, make_dirs
+
+class ConfigBase:
+    pass
+
+
+class ConfigAttrs(ConfigBase):
+    config_source = 'Installer'
+
+
+class ConfigCmdLine(ConfigBase):
+    """
+    Parse the command line, infering types from prior config (defaults)
+    """
+    config_source = 'Command Line'
+
+    def __init__(self, config):
+        for arg in sys.argv[1:]:
+            try:
+                a, v = arg.split('=')
+            except ValueError:
+                sys.exit('Unknown command line option: %r' % arg)
+            try:
+                t = type(getattr(config, a))
+            except AttributeError:
+                pass
+            else:
+                if t is bool:
+                    v = v.lower() in ('t', 'true', 'y', 'yes', '1')
+                else:
+                    try:
+                        v = t(v)
+                    except (ValueError, TypeError):
+                        pass
+            setattr(self, a, v)
+
+
+class Config:
+    def __init__(self):
+        self._sources = []
+        self._config_attrs = ConfigAttrs()
+
+    def source(self, prio, source):
+        assert source
+        pair = prio, source
+        i = bisect.bisect(self._sources, pair)
+        self._sources.insert(i, pair)
+
+    def source_attrs(self, prio):
+        self.source(prio, self._config_attrs)
+
+    def source_file(self, prio, name='config', path='', exclude=None):
+        if not os.path.isabs(path):
+            path = normjoin(self.base_dir, path)
+        try:
+            f, filename, extras = imp.find_module(name, [path])
+        except ImportError, e:
+            return
+        config_mod = imp.load_module(name, f, filename, extras)
+        config_mod.config_source = filename
+        if exclude:
+            for attr in exclude:
+                try:
+                    delattr(config_mod, attr)
+                except AttributeError:
+                    pass
+        self.source(prio, config_mod)
+
+    def source_cmdline(self, prio):
+        self.source(prio, ConfigCmdLine(self))
+
+    def __getattr__(self, a):
+        for prio, source in self._sources:
+            try:
+                return getattr(source, a)
+            except AttributeError:
+                pass
+        raise AttributeError('attribute "%s" not found' % a)
+
+    def __setattr__(self, a, v):
+        if a.startswith('_'):
+            self.__dict__[a] = v
+        else:
+            setattr(self._config_attrs, a, v)
+
+    def _config_dict(self):
+        """
+        Produce a dictionary of the current config
+        """
+        class _ConfigItem(object):
+            __slots__ = 'value', 'source'
+
+            def __init__(self, value, source):
+                self.value = value
+                self.source = source
+
+        config = {}
+        for prio, source in self._sources:
+            for a in dir(source):
+                if not config.has_key(a) and not a.startswith('_') \
+                    and a != 'config_source':
+                    v = getattr(source, a)
+                    if not callable(v):
+                        config[a] = _ConfigItem(v, source.config_source)
+        return config
+
+    def write_file(self, filename, exclude=None, owner=None, mode=None):
+        if not exclude:
+            exclude = ()
+        config = self._config_dict()
+        if self.install_prefix:
+            filename = self.install_prefix + filename
+        target_dir = os.path.dirname(filename)
+        make_dirs(target_dir, owner=owner)
+        fd, tmpname = tempfile.mkstemp(dir=target_dir)
+        f = os.fdopen(fd, 'w')
+        attributes = config.keys()
+        attributes.sort()
+        try:
+            for a in attributes:
+                for e in exclude:
+                    if fnmatch.fnmatch(a, e):
+                        break
+                else:
+                    f.write('%s=%r\n' % (a, config[a].value))
+            f.flush()
+            if owner is not None:
+                chown(tmpname, owner)
+            if mode is not None:
+                chmod(tmpname, mode)
+            os.rename(tmpname, filename)
+        finally:
+            f.close()
+            try:
+                os.unlink(tmpname)
+            except OSError:
+                pass
+
+    def __str__(self):
+        srcs = ';'.join(['%s[%s]' % (s.config_source, p)
+                         for p, s in self._sources])
+        config = self._config_dict()
+        attrs = config.keys()
+        attrs.sort()
+        attrs = ['\n    %s=%r (from %s)' % (a,config[a].value,config[a].source) 
+                 for a in attrs]
+        return '<%s %s%s>' % (self.__class__.__name__, srcs, ''.join(attrs))
+
+class Args:
+    pass
+
+def args_with_defaults(kwargs, config, arglist, conf_prefix = ''):
+    args = Args()
+    for argname in arglist:
+        try:
+            value = kwargs[argname]
+        except KeyError:
+            try:
+                value = getattr(config, conf_prefix + argname)
+            except AttributeError:
+                try:
+                    value = getattr(config, argname)
+                except AttributeError:
+                    value = None
+        setattr(args, argname, value)
+    return args
diff --git a/simpleinst/defaults.py b/simpleinst/defaults.py
new file mode 100644
index 0000000..37201e6
--- /dev/null
+++ b/simpleinst/defaults.py
@@ -0,0 +1,25 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import sys
+from os.path import dirname, abspath, normpath
+from distutils.sysconfig import get_config_var
+
+
+class Defaults:
+    config_source = 'Defaults'
+    install_mode = 0444
+    install_verbose = False
+    install_prefix = ''
+    base_dir = normpath(dirname(sys.modules['__main__'].__file__))
+    python = abspath(sys.executable)
+    bin_dir = get_config_var('BINDIR')
diff --git a/simpleinst/filter.py b/simpleinst/filter.py
new file mode 100644
index 0000000..df1ab2e
--- /dev/null
+++ b/simpleinst/filter.py
@@ -0,0 +1,33 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import re
+
+class _InstanceAsDict:
+    def __init__(self, inst):
+        self.inst = inst
+
+    def __getitem__(self, a):
+        try:
+            return getattr(self.inst, a)
+        except AttributeError:
+            raise KeyError(a)
+
+class Filter:
+    def __init__(self, config, pattern, subst, count = 0):
+        self.config = _InstanceAsDict(config)
+        self.pattern = re.compile(pattern, re.MULTILINE)
+        self.subst = subst
+        self.count = count
+
+    def filter(self, data):
+        return self.pattern.sub(self.subst % self.config, data, self.count)
diff --git a/simpleinst/glob.py b/simpleinst/glob.py
new file mode 100644
index 0000000..2326e45
--- /dev/null
+++ b/simpleinst/glob.py
@@ -0,0 +1,49 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+"""
+The standard glob doesn't support the concept of a base dir - something
+that we need in this application
+"""
+import os
+import re
+import fnmatch
+
+glob_re = re.compile('[*?[]')
+
+def glob(basedir, pattern):
+    def has_glob(pattern):
+        return glob_re.search(pattern) is not None
+
+    dirs = ['']
+    pattern_components = pattern.split(os.path.sep)
+    for pc in pattern_components:
+        new_dirs = []
+        if has_glob(pc):
+            for dir in dirs:
+                try:
+                    files = os.listdir(os.path.join(basedir, dir))
+                except OSError:
+                    continue
+                if not pattern.startswith('.'):
+                    files = [f for f in files if not f.startswith('.')]
+                new_dirs.extend([os.path.join(dir, f) 
+                                 for f in fnmatch.filter(files, pc)])
+        else:
+            for dir in dirs:
+                if os.path.exists(os.path.join(basedir, dir, pc)):
+                    new_dirs.append(os.path.join(dir, pc))
+        dirs = new_dirs
+    if not dirs:
+        return [pattern]
+    else:
+        return dirs
diff --git a/simpleinst/install_files.py b/simpleinst/install_files.py
new file mode 100644
index 0000000..0f32d59
--- /dev/null
+++ b/simpleinst/install_files.py
@@ -0,0 +1,190 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import os
+import stat
+import errno
+import tempfile
+import re
+from fnmatch import fnmatch
+from simpleinst.config_register import args_with_defaults
+from simpleinst.usergroup import user_lookup
+from simpleinst.glob import glob
+from simpleinst.filter import Filter
+from simpleinst.utils import *
+
+class PostInstallAction:
+    def __init__(self, pattern, fn, args, kwargs):
+        self.pattern = pattern
+        self.fn = fn
+        self.args = args
+        self.kwargs = kwargs
+
+    def match(self, filename):
+        return fnmatch(filename, self.pattern)
+
+    def action(self, filename, verbose):
+        if verbose:
+            print '    %s(%s)' % (self.fn.__name__, filename)
+        self.fn(filename, *self.args, **self.kwargs)
+
+class PostInstallActions:
+    def __init__(self, config):
+        self.actions = []
+        self.config = config
+
+    def add(self, pattern, fn, *args, **kwargs):
+        self.actions.append(PostInstallAction(pattern, fn, args, kwargs))
+
+    def post_install(self, filename, verbose=False):
+        for action in self.actions:
+            if action.match(filename):
+                action.action(filename, verbose=verbose)
+
+post_install_actions = None
+
+class FilenameFilter:
+    def __init__(self, include, exclude):
+        if type(include) in (str, unicode):
+            include = [include]
+        if type(exclude) in (str, unicode):
+            exclude = [exclude]
+        self.include_nop = not include
+        self.exclude_nop = not exclude
+        if not self.include_nop:
+            self.path_include = [i for i in include if i.find(os.path.sep) >= 0]
+            self.name_include = [i for i in include if i.find(os.path.sep) < 0]
+        if not self.exclude_nop:
+            self.path_exclude = [i for i in exclude if i.find(os.path.sep) >= 0]
+            self.name_exclude = [i for i in exclude if i.find(os.path.sep) < 0]
+    
+    def include(self, name):
+        basename = os.path.basename(name)
+
+        if not self.exclude_nop:
+            if basename != name:
+                for exclude in self.path_exclude:
+                    if fnmatch(name, exclude):
+                        return False
+            for exclude in self.name_exclude:
+                if fnmatch(basename, exclude):
+                    return False
+
+        if self.include_nop:
+            return True
+
+        if basename != name:
+            for include in self.path_include:
+                if fnmatch(name, include):
+                    return True
+        for include in self.name_include:
+            if fnmatch(basename, include):
+                return True
+
+        return False
+
+def copy(src, dst, 
+         owner = None, mode = None, filter = None, verbose = False,
+         bufsize = 1 << 22):
+    dst_dir = os.path.dirname(dst)
+    make_dirs(dst_dir, owner)
+    r_fd = os.open(src, os.O_RDONLY)
+    try:
+        st = os.fstat(r_fd)
+        try:
+            dst_st = os.stat(dst)
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
+        else:
+            # Same file? utime almost matches, and size matches...
+            if (abs(st.st_mtime - dst_st.st_mtime) <= 1 and
+                st.st_size == dst_st.st_size):
+                return False
+        w_fd, tmp_filename = tempfile.mkstemp(dir = dst_dir)
+        try:
+            while 1:
+                buf = os.read(r_fd, bufsize)
+                if not buf:
+                    break
+                if filter:
+                    if len(buf) == bufsize:
+                        raise IOError('Can\'t filter files larger than %s' %
+                                        bufsize - 1)
+                    for f in filter:
+                        buf = f.filter(buf)
+                os.write(w_fd, buf)
+            if mode:
+                chmod(tmp_filename, mode)
+            else:
+                os.chmod(tmp_filename, st.st_mode & 0777)
+            if owner:
+                os.chown(tmp_filename, *owner)
+            os.rename(tmp_filename, dst)
+            os.utime(dst, (st.st_atime, st.st_mtime))
+            if verbose:
+                print '    %s -> %s' % (src, dst_dir)
+            tmp_filename = None
+            return True
+        finally:
+            os.close(w_fd)
+            if tmp_filename:
+                os.unlink(tmp_filename)
+    finally:
+        os.close(r_fd)
+
+def recursive_copy(args, src):
+    fullsrc = normjoin(args.base, src)
+    src_path, src_file = os.path.split(src)
+    st = os.stat(fullsrc)
+    if stat.S_ISDIR(st.st_mode):
+        for filename in os.listdir(fullsrc):
+            recursive_copy(args, os.path.join(src, filename))
+    else:
+        if not args.filename_filter.include(src):
+            return
+        dst = normjoin(args.target, src)
+        if copy(fullsrc, dst, filter = args.filter,
+                owner = args.owner, mode = args.mode, 
+                verbose = args.verbose) and post_install_actions:
+            post_install_actions.post_install(dst, verbose=args.verbose)
+
+def install(config, **kwargs):
+    args = args_with_defaults(kwargs, config, 
+                              ('target', 'base', 'files', 'owner', 'mode', 
+                               'include', 'exclude', 'filter'),
+                              conf_prefix = 'install_')
+
+    if type(args.files) in (str, unicode):
+        args.files = [args.files]
+    if isinstance(args.filter, Filter):
+        args.filter = [args.filter]
+    if config.install_prefix:
+        args.target = config.install_prefix + args.target
+    args.verbose = getattr(config, 'install_verbose')
+    args.filename_filter = FilenameFilter(args.include, args.exclude)
+    if args.base:
+        args.base = normjoin(config.base_dir, args.base)
+    else:
+        args.base = config.base_dir
+    args.owner = user_lookup(args.owner)
+
+    for pat in args.files:
+        print 'installing %s to %s' % (normjoin(args.base, pat), args.target)
+        for src in glob(args.base, pat):
+            recursive_copy(args, src)
+
+def on_install(config, pattern, fn, *args, **kwargs):
+    global post_install_actions
+    if post_install_actions is None:
+        post_install_actions = PostInstallActions(config)
+    post_install_actions.add(pattern, fn, *args, **kwargs)
diff --git a/simpleinst/platform.py b/simpleinst/platform.py
new file mode 100644
index 0000000..8138e3d
--- /dev/null
+++ b/simpleinst/platform.py
@@ -0,0 +1,65 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import sys, os
+import pwd
+
+class PlatformBase:
+    def __init__(self):
+        self.config_source = '%s platform' % self.platform
+
+class RedHatLinux(PlatformBase):
+    platform = "RedHat Linux"
+    html_dir = '/var/www/html'
+    cgi_dir = '/var/www/cgi-bin'
+    web_user = 'apache'
+
+    def is_platform(self):
+        return sys.platform == 'linux2' \
+            and os.path.exists('/etc/redhat-release')
+
+class DebianLinux(PlatformBase):
+    platform = "Debian Linux"
+    html_dir = '/var/www'
+    cgi_dir = '/usr/lib/cgi-bin'
+    web_user = 'www-data'
+
+    def is_platform(self):
+        return sys.platform == 'linux2' \
+            and os.path.exists('/etc/debian_version')
+
+class OSX(PlatformBase):
+    platform = "Apple OS X"
+    html_dir = '/Library/WebServer/Documents'
+    cgi_dir = '/Library/WebServer/CGI-Executables'
+    web_user = 'www'
+
+    def is_platform(self):
+        if sys.platform != 'darwin':
+            return False
+        # Leopard returns _www for this:
+        self.web_user = pwd.getpwnam('www').pw_name
+        return True
+
+def get_platform():
+    platforms = []
+    for name, var in globals().items():
+        if hasattr(var, 'is_platform'):
+            platform = var()
+            if platform.is_platform():
+                platforms.append(platform)
+    if not platforms:
+        sys.exit('Unrecognised playform')
+    if len(platforms) > 1:
+        sys.exit('Ambiguous platform detection: %s' % \
+                 ', '.join([p.platform for p in platforms]))
+    return platforms[0]
diff --git a/simpleinst/pyinstaller.py b/simpleinst/pyinstaller.py
new file mode 100644
index 0000000..b765b25
--- /dev/null
+++ b/simpleinst/pyinstaller.py
@@ -0,0 +1,30 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+"""
+Import and run a user-defined python installer
+"""
+
+import os
+import imp
+
+def py_installer(config, name, *args, **kwargs):
+    print 'executing installer', name
+    path = os.path.join(config.base_dir, name)
+    gbals = {
+        '__name__': '__install__',
+        'config': config,
+        'args': args,
+        'kwargs': kwargs
+    }
+    return execfile(path, gbals)
+
diff --git a/simpleinst/usergroup.py b/simpleinst/usergroup.py
new file mode 100644
index 0000000..55f58b6
--- /dev/null
+++ b/simpleinst/usergroup.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import pwd, grp
+import os
+
+cache = {}
+
+def user_lookup(name):
+    try:
+        return cache[name]
+    except KeyError:
+        if name:
+            try:
+                user, group = name.split(':')
+            except ValueError:
+                user, group = name, None
+        else:
+            name = str(os.geteuid())
+        try:
+            uid = int(user)
+        except ValueError:
+            pw_ent = pwd.getpwnam(user)
+            uid, gid = pw_ent.pw_uid, pw_ent.pw_gid
+        else:
+            pw_ent = pwd.getpwuid(uid)
+            uid, gid = pw_ent.pw_uid, pw_ent.pw_gid
+
+        if group:
+            try:
+                gid = int(group)
+            except ValueError:
+                gid = grp.getgrnam(group).gr_gid
+            else:
+                gid = grp.getgrgid(gid).gr_gid
+
+        cache[name] = uid, gid
+        return uid, gid
diff --git a/simpleinst/utils.py b/simpleinst/utils.py
new file mode 100644
index 0000000..d12dc4e
--- /dev/null
+++ b/simpleinst/utils.py
@@ -0,0 +1,112 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "SimpleInst". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.  Copyright (C) 2004 Health
+#   Administration Corporation. All Rights Reserved.
+#
+import re
+import os
+import errno
+from simpleinst.usergroup import user_lookup
+
+__all__ = 'chown', 'chmod', 'normjoin', 'make_dirs'
+
+def normjoin(*args):
+    return os.path.normpath(os.path.join(*args))
+
+def chown(filename, owner):
+    if type(owner) in (unicode, str):
+        owner = user_lookup(owner)
+    os.chown(filename, *owner)
+
+chmod_re = re.compile('^([ugoa]*)([+-=])([rwxs]+)$')
+def chmod(filename, mode):
+    if type(mode) in (unicode, str):
+        if mode.startswith('0'):
+            mode = int(mode, 8)
+        else:
+            num_mode = 0400
+            for field in mode.split(','):
+                mask = 0
+                modes = 0
+                pre, mask_str, op, mode_str, post = chmod_re.split(field)
+                if mask_str:
+                    for m in mask_str:
+                        if m is 'u':
+                            mask |= 04700
+                        elif m is 'g':
+                            mask |= 02070
+                        elif m is 'o':
+                            mask |= 00007
+                        elif m is 'a':
+                            mask |= 06777
+                else:
+                    mask |= 06777
+                for m in mode_str:
+                    if m is 'r':
+                        modes |= 00444
+                    elif m is 'w':
+                        modes |= 00222
+                    elif m is 'x':
+                        modes |= 00111
+                    elif m is 's':
+                        modes |= 06000
+                if op is '+':
+                    num_mode |= modes & mask
+                elif op is '=':
+                    num_mode = modes & mask
+                elif op is '-':
+                    num_mode &= ~(modes & mask)
+            mode = num_mode
+    os.chmod(filename, mode)
+
+def make_dirs(dir, owner=None, config=None):
+    if config and config.install_prefix:
+        dir = config.install_prefix + dir
+    if type(owner) in (unicode, str):
+        owner = user_lookup(owner)
+    if not os.path.exists(dir):
+        par_dir = os.path.dirname(dir)
+        make_dirs(par_dir, owner)
+        os.mkdir(dir, 0755)
+        if owner is not None:
+            chown(dir, owner)
+
+def secret(nbits=256):
+    import binascii
+    f = open('/dev/urandom', 'rb')
+    try:
+        data = f.read(nbits / 8)
+    finally:
+        f.close()
+    return binascii.b2a_base64(data).rstrip()
+
+def getpass(prompt):
+    import getpass
+    return getpass.getpass(prompt)
+
+def collect(cmd):
+    f = os.popen(cmd, 'r')
+    try:
+        return ' '.join([l.rstrip() for l in f])
+    finally:
+        f.close()
+
+def rm_pyc(fn):
+    if fn.endswith('.py'):
+        try:
+            os.unlink(fn + 'c')
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
+        try:
+            os.unlink(fn + 'o')
+        except OSError, (eno, estr):
+            if eno != errno.ENOENT:
+                raise
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..aedeea5
--- /dev/null
+++ b/test.py
@@ -0,0 +1,67 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+import sys, os
+
+modpath = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir))
+sys.path.insert(0, modpath)
+
+all_tests = [
+    'tests.parse_addr.suite',
+    'tests.datetimetest.suite',
+    'tests.age',
+    'tests.person',
+    'tests.wikiformatting',
+    'tests.tuplestruct.suite',
+    'tests.dbobj.suite',
+    'tests.xmlparse.suite',
+    'tests.form.suite',
+    'tests.formsave.suite',
+    'tests.formlib.suite',
+    'tests.dataimp.xmlsaveload',
+    'tests.dataimp.datasrc',
+    'tests.dataimp.editor',
+    'tests.dataimp.dataimp',
+    'tests.searchacl.suite',
+    'tests.export.suite',
+    'tests.adminformedit.suite',
+    'tests.demogfields.suite',
+    'tests.reports.filters',
+    'tests.reports.orderby',
+    'tests.reports.columns',
+    'tests.reports.savenload',
+    'tests.reports.linereport',
+    'tests.reports.export',
+    'tests.reports.crosstab',
+    'tests.persondupe.suite',
+]
+
+def suite():
+    return unittest.defaultTestLoader.loadTestsFromNames(all_tests)
+
+if __name__ == '__main__':
+    try:
+        i = sys.argv.index('-d')
+    except ValueError:
+        pass
+    else:
+        # Allow test database (dsn) to be specified
+        import tests.testcommon
+        tests.testcommon.set_dsn(sys.argv[i+1])
+        del sys.argv[i:i+2]
+    unittest.main(module=None, defaultTest='__main__.suite') 
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/tests/adminformedit.py b/tests/adminformedit.py
new file mode 100644
index 0000000..0f316fc
--- /dev/null
+++ b/tests/adminformedit.py
@@ -0,0 +1,94 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import sys
+import unittest
+
+from cocklebur.form_ui.xmlsave import xmlsave
+from cocklebur.form_ui.xmlload import xmlload
+from casemgr.admin import formedit
+
+thisdir = os.path.dirname(__file__)
+formdir = os.path.join(thisdir, 'data', 'adminformedit')
+
+class Case(unittest.TestCase):
+    def load(self, version):
+        f = open(os.path.join(formdir, 'test%s.form' % version), 'r')
+        try:
+            return xmlload(f)
+        finally:
+            f.close()
+
+    def save(self, form, version):
+        f = open(os.path.join(formdir, 'test%s.form' % version), 'w')
+        try:
+            return xmlsave(f, form)
+        finally:
+            f.close()
+
+    def runTest(self):
+        # Load a form, turn it into a editable form and back into a form then
+        # check we still have an equivilent form.
+        form = self.load(0)
+        fe = formedit.Root(form)
+        self.assertEqual(form, fe.to_form())
+
+        # Add a new section
+        se = fe.new_section('E2')
+        se.node.text = 'New section'
+        self.assertEqual(self.load(1), fe.to_form())
+
+        # Copy new section - should have no effect
+        cut_buf = fe.copy('E2')
+        self.assertEqual(self.load(1), fe.to_form())
+
+        # Cut the new section and paste it back to the same place
+        cut_buf = fe.cut('E2')
+        #xmlsave(sys.stdout, cut_buf)
+        fe.paste('E2', cut_buf)
+        self.assertEqual(self.load(1), fe.to_form())
+
+        # Cut and paste before the previous question
+        cut_buf = fe.cut('E2')
+        fe.paste('E1', cut_buf)
+        self.assertEqual(self.load(2), fe.to_form())
+
+        # Cut the section again and paste it inside the first section
+        cut_buf = fe.cut('E1')
+        fe.paste('E0_2', cut_buf)
+        self.assertEqual(self.load(3), fe.to_form())
+
+        # Cut the section and paste it at the start of the form
+        cut_buf = fe.cut('E0_2')
+        fe.paste('E0', cut_buf)
+        self.assertEqual(self.load(4), fe.to_form())
+
+        # Duplicate the second section
+        cut_buf = fe.copy('E1')
+        fe.paste('E3', cut_buf)
+        self.assertEqual(self.load(5), fe.to_form())
+
+
+def suite():
+    return Case()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
+
diff --git a/tests/age.py b/tests/age.py
new file mode 100644
index 0000000..b805000
--- /dev/null
+++ b/tests/age.py
@@ -0,0 +1,85 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+
+from mx import DateTime
+
+from cocklebur import agelib
+
+from tests import testcommon
+
+class AgeTestCase(unittest.TestCase):
+    
+    def test_parse_age(self):
+        self.assertRaises(agelib.Error, agelib.Age.parse, '')
+        self.assertRaises(agelib.Error, agelib.Age.parse, 'x')
+        self.assertRaises(agelib.Error, agelib.Age.parse, 'x/y')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '1/y')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '1/1')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '1x')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '8/24')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '43/60')
+        self.assertRaises(agelib.Error, agelib.Age.parse, 'years')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '1 fortnight')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '140')
+        self.assertRaises(agelib.Error, agelib.Age.parse, '4/12/24')
+        self.assertEqual(agelib.Age.parse('4/12'), (4, 'm'))
+        self.assertEqual(agelib.Age.parse('3/52'), (3, 'w'))
+        self.assertEqual(agelib.Age.parse('5/7'), (5, 'd'))
+        self.assertEqual(agelib.Age.parse('6'), (6, 'y'))
+        self.assertEqual(agelib.Age.parse('41'), (41, 'y'))
+        self.assertEqual(agelib.Age.parse('6y'), (6, 'y'))
+        self.assertEqual(agelib.Age.parse('6 years'), (6, 'y'))
+        self.assertEqual(agelib.Age.parse('3w'), (3, 'w'))
+        self.assertEqual(agelib.Age.parse('3 weeks'), (3, 'w'))
+        self.assertEqual(agelib.Age.parse('4m'), (4, 'm'))
+        self.assertEqual(agelib.Age.parse('4 Months'), (4, 'm'))
+        self.assertEqual(agelib.Age.parse('5d'), (5, 'd'))
+        self.assertEqual(agelib.Age.parse('5days'), (5, 'd'))
+
+    def test_agestr(self):
+        dt = DateTime.DateTime
+        now = dt(2010,7,16,13,52,38)
+        self.assertEqual(agelib.agestr(None, now), None)
+        self.assertEqual(agelib.agestr('', now), None)
+        self.assertEqual(agelib.agestr(dt(2010,7,17,0,0,0), now), '??')
+        self.assertEqual(agelib.agestr(dt(2010,7,16,6,0,0), now), '0d')
+        self.assertEqual(agelib.agestr(dt(2010,7,14,0,0,0), now), '2d')
+        self.assertEqual(agelib.agestr(dt(2010,6,16,0,0,0), now), '4w')
+        self.assertEqual(agelib.agestr(dt(2010,4,17,0,0,0), now), '12w')
+        self.assertEqual(agelib.agestr(dt(2010,4,16,0,0,0), now), '3m')
+        self.assertEqual(agelib.agestr(dt(2007,7,17,6,0,0), now), '35m')
+        self.assertEqual(agelib.agestr(dt(2007,7,16,6,0,0), now), '3y')
+
+    def test_parse_dob_or_age(self):
+        dt = DateTime.DateTime
+        now = dt(2010,7,16,13,52,38)
+        parse = agelib.parse_dob_or_age
+        self.assertEqual(parse(None, now), (None, None))
+        self.assertEqual(parse('1d', now), (dt(2010,7,15,13,52,38), 1))
+        self.assertEqual(parse('1 day', now), (dt(2010,7,15,13,52,38), 1))
+        self.assertEqual(parse('1/7', now), (dt(2010,7,15,13,52,38), 1))
+        self.assertEqual(parse('1w', now), (dt(2010,7,9,13,52,38), 7))
+        self.assertEqual(parse('1 week', now), (dt(2010,7,9,13,52,38), 7))
+        self.assertEqual(parse('1m', now), (dt(2010,6,16,13,52,38), 31))
+        self.assertEqual(parse('1 month', now), (dt(2010,6,16,13,52,38), 31))
+        self.assertEqual(parse('1y', now), (dt(2009,7,16,13,52,38), 366))
+        self.assertEqual(parse('1 year', now), (dt(2009,7,16,13,52,38), 366))
+        self.assertEqual(parse('2001-2-3', now), (dt(2001,2,3), 0))
+
+
diff --git a/tests/data/adminformedit/test0.form b/tests/data/adminformedit/test0.form
new file mode 100644
index 0000000..56bf7d0
--- /dev/null
+++ b/tests/data/adminformedit/test0.form
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<form name='testform' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/tests/data/adminformedit/test1.form b/tests/data/adminformedit/test1.form
new file mode 100644
index 0000000..fc4b5ba
--- /dev/null
+++ b/tests/data/adminformedit/test1.form
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<form name='None' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+ <section>
+  <label>New section</label>
+ </section>
+</form>
diff --git a/tests/data/adminformedit/test2.form b/tests/data/adminformedit/test2.form
new file mode 100644
index 0000000..d30d398
--- /dev/null
+++ b/tests/data/adminformedit/test2.form
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<form name='None' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+ <section>
+  <label>New section</label>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/tests/data/adminformedit/test3.form b/tests/data/adminformedit/test3.form
new file mode 100644
index 0000000..752140e
--- /dev/null
+++ b/tests/data/adminformedit/test3.form
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<form name='None' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+  <section>
+   <label>New section</label>
+  </section>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/tests/data/adminformedit/test4.form b/tests/data/adminformedit/test4.form
new file mode 100644
index 0000000..c4f594e
--- /dev/null
+++ b/tests/data/adminformedit/test4.form
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<form name='None' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>New section</label>
+ </section>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/tests/data/adminformedit/test5.form b/tests/data/adminformedit/test5.form
new file mode 100644
index 0000000..9713f27
--- /dev/null
+++ b/tests/data/adminformedit/test5.form
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<form name='None' form_type='testform' allow_multiple='False'>
+ <label>Test Form</label>
+ <section>
+  <label>New section</label>
+ </section>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+ <question>
+  <label>Third Question</label>
+  <input name='q3' type='RadioList' required='True' direction='horizontal'>
+   <choices>
+    <choice name='a'>A</choice>
+    <choice name='b'>B</choice>
+    <choice name='c'>C</choice>
+   </choices>
+  </input>
+ </question>
+ <section>
+  <label>Section 1</label>
+  <question>
+   <label>First Question</label>
+   <input name='input_a' type='YesNo' required='True'>
+    <summary>Input A</summary>
+   </input>
+  </question>
+  <question>
+   <label>Second Question</label>
+   <input name='q2a' type='TextInput'>
+    <pre_text>Part A</pre_text>
+   </input>
+   <input name='q2b' type='TextInput'>
+    <pre_text>Part B</pre_text>
+   </input>
+   <input name='q2c' type='TextInput'>
+    <pre_text>Part C</pre_text>
+   </input>
+  </question>
+ </section>
+</form>
diff --git a/tests/data/export/doharesult.csv b/tests/data/export/doharesult.csv
new file mode 100644
index 0000000..cfec86e
--- /dev/null
+++ b/tests/data/export/doharesult.csv
@@ -0,0 +1,4 @@
+case_id,deleted,delete_reason,delete_timestamp,local_case_id,syndrome_id,case_status,case_assignment,notification_datetime,onset_datetime,notifier_name,notifier_contact,notes,person_id,data_src,surname,given_names,dob,dob_prec,sex,interpreter_req,home_phone,work_phone,mobile_phone,fax_phone,e_mail,street_address,locality,state,postcode,country,alt_street_address,alt_locality,alt_state,alt_postcode,alt_country,work_street_address,work_locality,work_state,work_postcode,work_country,occupat [...]
+1,,,,case_A,2,,,2003-04-03 00:00:00,2003-04-02 00:00:00,,,,1,,Person_Surname_A,Person_Given_Name_A,1958-04-03 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagTwo,sars_exposure,1,F11,2003-04-03 00:00:00,True,23.1,2003-03-01 00:00:00,2003-03-02 00:00:00,,,,sars_exposure,1,F44,2001-07-06 00:00:00,Unknown,29.1,2001-01-02 00:00:00,2003-03-02 00:00:00,,,
+2,,,,case_B,2,,,2003-05-04 00:00:00,2003-05-03 00:00:00,,,,2,,Person_Surname_B,Person_Given_Name_B,1970-12-04 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagOne TagTwo,sars_exposure,,,,,,,,,,,sars_exposure,,,,,,,,,,
+5,,,,case_E_diff_unit_acl,2,,,2003-06-05 00:00:00,2003-06-04 00:00:00,,,,2,,Person_Surname_B,Person_Given_Name_B,1970-12-04 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,,sars_exposure,1,F33,2002-06-05 00:00:00,False,2.4,2002-04-02 00:00:00,2002-04-05 00:00:00,,,,sars_exposure,,,,,,,,,,
diff --git a/tests/data/export/formresult.csv b/tests/data/export/formresult.csv
new file mode 100644
index 0000000..0196de6
--- /dev/null
+++ b/tests/data/export/formresult.csv
@@ -0,0 +1,4 @@
+case_id,deleted,delete_reason,delete_timestamp,local_case_id,syndrome_id,case_status,case_assignment,notification_datetime,onset_datetime,notifier_name,notifier_contact,notes,person_id,data_src,surname,given_names,dob,dob_prec,sex,interpreter_req,home_phone,work_phone,mobile_phone,fax_phone,e_mail,street_address,locality,state,postcode,country,alt_street_address,alt_locality,alt_state,alt_postcode,alt_country,work_street_address,work_locality,work_state,work_postcode,work_country,occupat [...]
+1,,,,case_A,2,,,2003-04-03 00:00:00,2003-04-02 00:00:00,,,,1,,Person_Surname_A,Person_Given_Name_A,1958-04-03 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagTwo,F11,2003-04-03 00:00:00,True,23.1,2003-03-01 00:00:00,2003-03-02 00:00:00,,,
+1,,,,case_A,2,,,2003-04-03 00:00:00,2003-04-02 00:00:00,,,,1,,Person_Surname_A,Person_Given_Name_A,1958-04-03 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagTwo,F44,2001-07-06 00:00:00,Unknown,29.1,2001-01-02 00:00:00,2003-03-02 00:00:00,,,
+5,,,,case_E_diff_unit_acl,2,,,2003-06-05 00:00:00,2003-06-04 00:00:00,,,,2,,Person_Surname_B,Person_Given_Name_B,1970-12-04 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,,F33,2002-06-05 00:00:00,False,2.4,2002-04-02 00:00:00,2002-04-05 00:00:00,,,
diff --git a/tests/data/export/result.csv b/tests/data/export/result.csv
new file mode 100644
index 0000000..45c12fd
--- /dev/null
+++ b/tests/data/export/result.csv
@@ -0,0 +1,4 @@
+case_id,deleted,delete_reason,delete_timestamp,local_case_id,syndrome_id,case_status,case_assignment,notification_datetime,onset_datetime,notifier_name,notifier_contact,notes,person_id,data_src,surname,given_names,dob,dob_prec,sex,interpreter_req,home_phone,work_phone,mobile_phone,fax_phone,e_mail,street_address,locality,state,postcode,country,alt_street_address,alt_locality,alt_state,alt_postcode,alt_country,work_street_address,work_locality,work_state,work_postcode,work_country,occupat [...]
+1,,,,case_A,2,,,2003-04-03 00:00:00,2003-04-02 00:00:00,,,,1,,Person_Surname_A,Person_Given_Name_A,1958-04-03 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagTwo,F11,2003-04-03 00:00:00,True,23.1,2003-03-01 00:00:00,2003-03-02 00:00:00,,,,F44,2001-07-06 00:00:00,Unknown,29.1,2001-01-02 00:00:00,2003-03-02 00:00:00,,,
+2,,,,case_B,2,,,2003-05-04 00:00:00,2003-05-03 00:00:00,,,,2,,Person_Surname_B,Person_Given_Name_B,1970-12-04 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,TagOne TagTwo,,,,,,,,,,,,,,,,,,
+5,,,,case_E_diff_unit_acl,2,,,2003-06-05 00:00:00,2003-06-04 00:00:00,,,,2,,Person_Surname_B,Person_Given_Name_B,1970-12-04 00:00:00,,M,,,,,,,,,,,,,,,,,,,,,,,,,,,,,F33,2002-06-05 00:00:00,False,2.4,2002-04-02 00:00:00,2002-04-05 00:00:00,,,,,,,,,,,,
diff --git a/tests/data/hospital_admit.form b/tests/data/hospital_admit.form
new file mode 100644
index 0000000..a92d875
--- /dev/null
+++ b/tests/data/hospital_admit.form
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<form name='hospital_admit' form_type='case' allow_multiple='True'>
+ <label>Hospital Admission (SARS)</label>
+ <question>
+  <label>Hospitalised</label>
+  <input name='admission_hospitalised' type='YesNo'>
+   <summary>hospitalised</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date hospitalised:</label>
+  <input name='admission_date' type='DateInput'>
+   <summary>admitted</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date discharged:</label>
+  <input name='admission_discharge_date' type='DateInput'>
+   <summary>discharged</summary>
+  </input>
+ </question>
+ <question>
+  <label>Hospital name:</label>
+  <input name='admission_hospital' type='TextInput' />
+ </question>
+ <question>
+  <label>Length of hospitalisation:</label>
+  <input name='admission_stay' type='IntInput'>
+   <post_text>days</post_text>
+  </input>
+ </question>
+ <question>
+  <label>Isolation</label>
+  <input name='admission_isolation' type='YesNo' />
+ </question>
+ <question>
+  <label>Mechanical Ventilation</label>
+  <input name='admission_mech_vent' type='YesNo' />
+ </question>
+ <question>
+  <label>Aerosol producing procedures</label>
+  <input name='admission_aerosol' type='YesNo' />
+  <input name='admission_aerosol_detail' type='TextInput'>
+   <pre_text>If yes, specify:</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>ICU admission</label>
+  <input name='admission_icu' type='YesNo' />
+  <input name='admission_icu_stay' type='IntInput'>
+   <pre_text>If yes, length of stay:</pre_text>
+  </input>
+ </question>
+ <question>
+  <label>Co-morbidities</label>
+  <input name='admission_co_morb' type='TextArea' />
+ </question>
+</form>
diff --git a/tests/data/sars_exposure.form b/tests/data/sars_exposure.form
new file mode 100644
index 0000000..66f7c30
--- /dev/null
+++ b/tests/data/sars_exposure.form
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<form name='sars_exposure' form_type='case' allow_multiple='False'>
+ <label>Exposure History (SARS)</label>
+ <question>
+  <label>
+   Close contact with person diagnosed with SARS in 10 days prior to onset.
+   [Close contact is defined as....]
+  </label>
+  <input name='Close_contact' type='RadioList' required='True'>
+   <summary>Contact with case</summary>
+   <choices>
+    <choice name='True'>Yes</choice>
+    <choice name='False'>No</choice>
+    <choice name='Unknown'>Unknown</choice>
+   </choices>
+  </input>
+ </question>
+ <question>
+  <label>Duration of contact (in hours)</label>
+  <input name='Contact_duration' type='FloatInput'>
+   <summary>Contact duration (hours)</summary>
+   <post_text>hours</post_text>
+  </input>
+ </question>
+ <question>
+  <label>Date of first contact with person with SARS</label>
+  <input name='Contact_date_first' type='DateInput'>
+   <summary>Date of first contact</summary>
+  </input>
+ </question>
+ <question>
+  <label>Date of last contact with a person with SARS</label>
+  <input name='Contact_date_last' type='DateInput'>
+   <summary>Most recent contact</summary>
+  </input>
+ </question>
+ <question>
+  <label>Favourite foods</label>
+  <input name='Contact_favourite_food' type='CheckBoxes'>
+   <summary>Favourite foods</summary>
+   <choices>
+    <choice name='icecream'>Icecream</choice>
+    <choice name='lambchops'>Lamb Chops</choice>
+    <choice name='apples'>Apples</choice>
+   </choices>
+  </input>
+ </question>
+</form>
diff --git a/tests/data/tables/address_states.csv b/tests/data/tables/address_states.csv
new file mode 100644
index 0000000..4462c98
--- /dev/null
+++ b/tests/data/tables/address_states.csv
@@ -0,0 +1,9 @@
+code,label
+NSW,New South Wales
+ACT,Australian Capital Territory
+NT,Northern Territory
+QLD,Queensland
+SA,South Australia
+TAS,Tasmania
+VIC,Victoria
+WA,Western Australia
diff --git a/tests/data/tables/case_acl.csv b/tests/data/tables/case_acl.csv
new file mode 100644
index 0000000..71b522b
--- /dev/null
+++ b/tests/data/tables/case_acl.csv
@@ -0,0 +1,6 @@
+case_acl_id,case_id,unit_id
+1,1,1
+2,2,1
+3,3,1
+4,4,2
+5,5,1
diff --git a/tests/data/tables/case_form_summary.csv b/tests/data/tables/case_form_summary.csv
new file mode 100644
index 0000000..4cf1f49
--- /dev/null
+++ b/tests/data/tables/case_form_summary.csv
@@ -0,0 +1,5 @@
+summary_id,case_id,form_label,form_version,deleted
+1,1,sars_exposure,1,
+2,1,hospital_admit,1,
+3,5,sars_exposure,1,
+4,1,sars_exposure,1,
diff --git a/tests/data/tables/case_tags.csv b/tests/data/tables/case_tags.csv
new file mode 100644
index 0000000..72c2004
--- /dev/null
+++ b/tests/data/tables/case_tags.csv
@@ -0,0 +1,4 @@
+case_tag_id,case_id,tag_id
+1,1,2
+2,2,1
+3,2,2
diff --git a/tests/data/tables/cases.csv b/tests/data/tables/cases.csv
new file mode 100644
index 0000000..0409700
--- /dev/null
+++ b/tests/data/tables/cases.csv
@@ -0,0 +1,6 @@
+case_id,local_case_id,person_id,syndrome_id,case_assignment,notification_datetime,onset_datetime
+1,case_A,1,2,,3/4/2003,2/4/2003
+2,case_B,2,2,,4/5/2003,3/5/2003
+3,case_C_diff_syndrome,2,1,,5/6/2003,4/6/2003
+4,case_D_diff_unit,2,2,,5/6/2003,4/6/2003
+5,case_E_diff_unit_acl,2,2,,5/6/2003,4/6/2003
diff --git a/tests/data/tables/forms.csv b/tests/data/tables/forms.csv
new file mode 100644
index 0000000..ff02198
--- /dev/null
+++ b/tests/data/tables/forms.csv
@@ -0,0 +1,3 @@
+label,form_type,cur_version,name,allow_multiple 
+sars_exposure,case,1,Exposure History (SARS),1
+hospital_admit,case,1,Hospital admission,1
diff --git a/tests/data/tables/hospital_admit.csv b/tests/data/tables/hospital_admit.csv
new file mode 100644
index 0000000..f4c879f
--- /dev/null
+++ b/tests/data/tables/hospital_admit.csv
@@ -0,0 +1 @@
+summary_id,form_date,admission_hospitalised,admission_date,admission_discharge_date,admission_hospital,admission_stay,admission_isolation,admission_mech_vent,admission_aerosol,admission_aerosol_detail,admission_icu,admission_icu_stay,admission_co_morb
diff --git a/tests/data/tables/persons.csv b/tests/data/tables/persons.csv
new file mode 100644
index 0000000..816546b
--- /dev/null
+++ b/tests/data/tables/persons.csv
@@ -0,0 +1,4 @@
+person_id,surname,given_names,dob,sex
+1,Person_Surname_A,Person_Given_Name_A,3/4/58,M
+2,Person_Surname_B,Person_Given_Name_B,4/12/70,M
+3,Person_Surname_C,Person_Given_Name_C,5/2/80,F
diff --git a/tests/data/tables/sars_exposure.csv b/tests/data/tables/sars_exposure.csv
new file mode 100644
index 0000000..726a768
--- /dev/null
+++ b/tests/data/tables/sars_exposure.csv
@@ -0,0 +1,5 @@
+summary_id,form_date,close_contact,contact_duration,contact_date_first,contact_date_last
+1,3/4/2003,True,23.1,1/3/2003,2/3/2003
+2,4/5/2004,False,12.3,2/4/2004,5/4/2004
+3,5/6/2002,False,2.4,2/4/2002,5/4/2002
+4,6/7/2001,Unknown,29.1,2/1/2001,2/3/2003
diff --git a/tests/data/tables/syndrome_case_assignments.csv b/tests/data/tables/syndrome_case_assignments.csv
new file mode 100644
index 0000000..b88f93b
--- /dev/null
+++ b/tests/data/tables/syndrome_case_assignments.csv
@@ -0,0 +1,19 @@
+name,label
+GSAHS_G,Greater Southern AHS (Goulburn)
+GSAHS_A,Greater Southern AHS (Albury)
+GWAHS_BH,Greater Western AHS (Broken Hill)
+GWAHS_D,Greater Western AHS (Dubbo)
+GWAHS_B,Greater Western AHS (Bathurst)
+HNEAHS_N,Hunter/New England AHS (Newcastle)
+HNEAHS_T,Hunter/New England AHS (Tamworth)
+JH,Justice Health Service
+NCAHS_PM,North Coast AHS (Port Macquarie)
+NCAHS_L,North Coast AHS (Lismore)
+NSCCAHS_H,Northern Sydney/Central Coast AHS (Hornsby)
+NSCCAHS_G,Northern Sydney/Central Coast AHS (Gosford)
+SESIAHS_R,South Eastern Sydney/Illawarra AHS (Randwick)
+SESIAHS_W,South Eastern Sydney/Illawarra AHS (Wollongong)
+SSWAHS_C,Sydney South West AHS (Camperdown)
+SWAHS_PA,Sydney West AHS (Parramatta)
+SWAHS_PE,Sydney West AHS (Penrith)
+OTHER,Other
diff --git a/tests/data/tables/syndrome_case_status.csv b/tests/data/tables/syndrome_case_status.csv
new file mode 100644
index 0000000..2f29759
--- /dev/null
+++ b/tests/data/tables/syndrome_case_status.csv
@@ -0,0 +1,5 @@
+name,label
+excluded,Excluded
+preliminary,Preliminary
+suspected,Suspected
+confirmed,Confirmed
diff --git a/tests/data/tables/syndrome_forms.csv b/tests/data/tables/syndrome_forms.csv
new file mode 100644
index 0000000..680fb12
--- /dev/null
+++ b/tests/data/tables/syndrome_forms.csv
@@ -0,0 +1,4 @@
+syndrome_forms_id,syndrome_id,form_label
+1,1,sars_exposure
+2,2,sars_exposure
+3,2,hospital_admit
diff --git a/tests/data/tables/syndrome_types.csv b/tests/data/tables/syndrome_types.csv
new file mode 100644
index 0000000..93f16ae
--- /dev/null
+++ b/tests/data/tables/syndrome_types.csv
@@ -0,0 +1,3 @@
+syndrome_id,name
+1,Smallpox
+2,SARS
diff --git a/tests/data/tables/tags.csv b/tests/data/tables/tags.csv
new file mode 100644
index 0000000..93713e9
--- /dev/null
+++ b/tests/data/tables/tags.csv
@@ -0,0 +1,4 @@
+tag_id,tag
+1,TagOne
+2,TagTwo
+3,TagThree
diff --git a/tests/data/tables/units.csv b/tests/data/tables/units.csv
new file mode 100644
index 0000000..32721fa
--- /dev/null
+++ b/tests/data/tables/units.csv
@@ -0,0 +1,4 @@
+unit_id,enabled,name
+0,1,Dept of Health
+1,1,Unit A
+2,1,Unit B
diff --git a/tests/data/tables/users.csv b/tests/data/tables/users.csv
new file mode 100644
index 0000000..615fa62
--- /dev/null
+++ b/tests/data/tables/users.csv
@@ -0,0 +1,3 @@
+user_id,enabled,username,fullname
+1,1,test_user,Test User
+2,1,other_user,Other User
diff --git a/tests/dataimp/__init__.py b/tests/dataimp/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/tests/dataimp/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/tests/dataimp/dataimp.py b/tests/dataimp/dataimp.py
new file mode 100644
index 0000000..9a920e6
--- /dev/null
+++ b/tests/dataimp/dataimp.py
@@ -0,0 +1,307 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+import os
+import shutil
+import unittest
+from cStringIO import StringIO
+
+from mx.DateTime.ISO import ParseDateTime as dt
+from mx import DateTime
+
+from cocklebur import dbobj
+from casemgr.dataimp import dataimp, datasrc
+from casemgr.dataimp.xmlload import xmlload
+from casemgr.dataimp.elements import *
+
+import config
+
+from tests import testcommon
+
+
+
+import_named_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" 
+    fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="surname" src="Surname" />
+ <agesource field="DOB" src="DoB" age="Age">
+  <date format="DD/MM/YYYY"/>
+ </agesource>
+ <source field="case_status" src="Status">
+  <translate match="suspected" to="preliminary" ignorecase="yes" />
+ </source>
+ <fixed field="locality" value="NSW" />
+ <ignore field="street_address" />
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="Duration" />
+  <fixed field="Close_contact" value="Unknown" />
+  <ignore field="Contact_date_first" />
+  <multivalue field="Contact_favourite_food" src="Likes" delimiter="/" />
+ </form>
+</importrules>
+'''
+
+import_positional_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="positional" encoding="utf-8" 
+    fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="surname" src="1" />
+ <agesource field="DOB" src="3" age="4">
+  <date format="DD/MM/YYYY"/>
+ </agesource>
+ <source field="case_status" src="2">
+  <translate match="suspected" to="preliminary" ignorecase="yes" />
+ </source>
+ <fixed field="locality" value="NSW" />
+ <ignore field="street_address" />
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="5" />
+  <fixed field="Close_contact" value="Unknown" />
+  <ignore field="Contact_date_first" />
+  <multivalue field="Contact_favourite_food" src="7" delimiter="/" />
+ </form>
+</importrules>
+'''
+
+data = '''\
+Surname,Status,DoB,Age,Duration,Id,Likes
+blogs,confirmed,24/11/2001,,1,100,Icecream
+smith,confirmed,,3,2,101,Lambchops/Apples
+jones,suspected,4m,,,102,
+'''
+
+data_update = '''\
+smith,confirmed,20/1/2000,,2,101,
+williams,excluded,2/12/1940,,3,104,apples
+'''
+
+error_named_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" 
+    fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="surname" src="Surname" />
+ <agesource field="DOB" src="DoB" age="Age">
+  <date format="DD/MM/YYYY"/>
+ </agesource>
+ <source field="case_status" src="Status">
+  <translate match="suspected" to="preliminary" ignorecase="yes" />
+ </source>
+ <fixed field="locality" value="NSW" />
+ <ignore field="street_address" />
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="Duration" />
+  <source field="Close_contact" src="Contact" />
+  <source field="Contact_date_first" src="Date" />
+  <multivalue field="Contact_favourite_food" src="Likes" delimiter="/" />
+ </form>
+</importrules>
+'''
+
+# Need to reproduce the effects of casemgr.person DOB processing, as well as
+# cocklebur.datetime, cocklebur.dbobj.column_describer and a round-trip through
+# postgres - urgh.
+truncate_tod = DateTime.RelativeDateTime(hour=0, minute=0, second=0)
+three_years = DateTime.RelativeDateTime(years=3)
+age3y = DateTime.now() - three_years - truncate_tod
+four_months = DateTime.RelativeDateTime(months=4)
+age4m = DateTime.now() - four_months - truncate_tod
+
+class DataImpTest(testcommon.AppTestCase):
+
+    create_tables = testcommon.AppTestCase.create_tables + (
+        'nicknames',
+        'person_phonetics',
+        'syndrome_case_status',
+        'syndrome_demog_fields',
+        'import_defs',
+    )
+
+    scratchdir = os.path.join(os.path.dirname(__file__), 'scratch')
+
+    def setUp(self):
+        testcommon.AppTestCase.setUp(self)
+        config.scratchdir = self.scratchdir
+        os.mkdir(config.scratchdir)
+
+    def tearDown(self):
+        shutil.rmtree(self.scratchdir)
+        testcommon.AppTestCase.tearDown(self)
+
+    def dumprules(self, rules):
+        # Debugging aid
+        from casemgr.dataimp.xmlsave import xmlsave
+        xmlsave(f, rules)
+        print f.getvalue()
+
+    def test_errors(self):
+        rules = xmlload(StringIO(import_named_xml))
+        cred = testcommon.DummyCredentials()
+        imp = dataimp.PreviewImport(cred, 1, datasrc.NullDataImpSrc, rules)
+        self.assertEqual(list(imp.errors), ['No data source selected'])
+
+    def _test_preview(self, rules_xml, data):
+        cred = testcommon.DummyCredentials()
+        rules = xmlload(StringIO(rules_xml))
+        src = datasrc.DataImpSrc('foo', StringIO(data))
+        now = DateTime.DateTime(2010,7,20,17,23,1)
+        imp = testcommon.freeze_time(now, dataimp.PreviewImport, 
+                                     cred, 1, src, rules)
+        self.failIf(imp.errors, imp.errors)
+        self.assertEqual(imp.group_header, [('Demographics', 4),
+                                            ('Exposure History (SARS)', 3)])
+        self.assertEqual(imp.header, [
+            'Status', 'Surname', 'Date of birth/Age', 'Locality/Suburb',
+            'Contact with case',
+            'Contact duration (hours)',
+            'Favourite foods',
+        ])
+        self.assertEqual(imp.rows, [
+            ['Confirmed', 'BLOGS', '24/11/2001 (8y)', 'NSW', 
+                'Unknown', '1', 'Icecream'], 
+            ['Confirmed', 'SMITH', '3 years', 'NSW', 
+                'Unknown', '2', 'Lamb Chops/Apples'], 
+            ['Preliminary', 'JONES', '4 months', 'NSW', 
+                'Unknown', None, None]
+        ])
+
+    def test_preview_named(self):
+        self._test_preview(import_named_xml, data)
+
+    def test_preview_positional(self):
+        positional_data = '\n'.join(data.splitlines()[1:])
+        self._test_preview(import_positional_xml, positional_data)
+
+    def test_preview_errors(self):
+        cred = testcommon.DummyCredentials()
+        rules = xmlload(StringIO(error_named_xml))
+        data = '''\
+Surname,Status,DoB,Age,Duration,Contact,Date,Id,Likes
+blogs,XXX,24/11/2001,,,False,,100,
+smith,confirmed,,10000,2,True,,101,Apples
+jones,suspected,2008-1-30,,,Unknown,,102,Tomato
+,,,,,,,,
+williams,,,,a,XX,XX,103,
+,,,,,,
+'''
+        src = datasrc.DataImpSrc('foo', StringIO(data))
+        imp = dataimp.PreviewImport(cred, 1, src, rules)
+        self.assertListEq(list(imp.errors), [
+            'foo: record 5 (line 7): Column count is not constant: has 7 columns, expected 9',
+            "record 1 (line 2): Status: 'XXX' not a valid choice",
+            "record 2 (line 3): Date of birth/Age: date/time '10000' does not match format 'DD/MM/YYYY'",
+            "record 3 (line 4): Date of birth/Age: date/time '2008-1-30' does not match format 'DD/MM/YYYY'",
+            'record 3 (line 4): Favourite foods: tomato not valid choice(s)',
+            'record 4 (line 5): Either Surname or Local ID must be specified',
+            'record 4 (line 5): Exposure History (SARS): Contact with case: this field must be answered',
+            "record 5 (line 6): Contact with case: 'XX' not a valid choice",
+            'record 5 (line 6): Exposure History (SARS): Contact with case: this field must be answered',
+            'record 5 (line 6): Exposure History (SARS): Contact duration (hours): value must be a number',
+            'record 5 (line 6): Exposure History (SARS): Date of first contact: could not parse date "XX"',
+        ])
+        self.assertEqual(imp.errors.count(), 11)
+        self.failUnless(0 not in imp.errors)
+        self.failUnless(1 in imp.errors)
+        self.failUnless(5 in imp.errors)
+        self.failUnless(6 not in imp.errors)
+        self.assertEqual(imp.errors.get(4), [
+            'record 4 (line 5): Either Surname or Local ID must be specified',
+            'record 4 (line 5): Exposure History (SARS): Contact with case: this field must be answered',
+        ])
+        # Check "too many errors" handling
+        lines = data.splitlines()
+        data = '\n'.join([lines[0]] + [lines[1]] * 101)
+        src = datasrc.DataImpSrc('foo', StringIO(data))
+        imp = dataimp.PreviewImport(cred, 1, src, rules)
+        self.assertEqual(list(imp.errors)[0], 
+            'More than %s errors, giving up' % imp.errors.MAX_ERRORS)
+        self.assertEqual(imp.errors.count(), 100)
+
+
+    def _fetch_rows(self):
+        query = self.db.query('persons', order_by='person_id')
+        query.join('JOIN cases USING (person_id)')
+        query.join('LEFT JOIN case_form_summary USING (case_id)')
+        query.join('LEFT JOIN form_sars_exposure_00001 USING (summary_id)')
+        query.where('persons.data_src = %s', 'import')
+        cols = (
+            'surname', 'DOB', 'DOB_prec', 'case_status', 'locality',
+            'street_address', 'local_case_id',
+            'Contact_duration', 'Close_contact', 'Contact_date_first',
+            'Contact_favourite_foodicecream',
+            'Contact_favourite_foodapples',
+            'Contact_favourite_foodlambchops',
+        )
+        return query.fetchcols(cols)
+
+    def test_import_named(self):
+        cred = testcommon.DummyCredentials()
+        rules = xmlload(StringIO(import_named_xml))
+        src = datasrc.DataImpSrc('foo', StringIO(data))
+        imp = dataimp.DataImp(cred, 1, src, rules)
+        self.failIf(imp.errors, imp.errors)
+        self.assertEqual(imp.errors.count(), 0)
+        self.assertEqual(imp.new_cnt, 3)
+        self.assertEqual(imp.update_cnt, 0)
+        self.assertListEq(self._fetch_rows(), [
+            ('BLOGS', dt('2001-11-24'), 0, 'confirmed', 'NSW', None, None,
+                1.0, 'Unknown', None, True, False, False), 
+            ('SMITH', age3y, 366, 'confirmed', 'NSW', None, None,
+                2.0, 'Unknown', None, False, True, True), 
+            ('JONES', age4m, 31, 'preliminary', 'NSW', None, None,
+                None, 'Unknown', None, False, False, False),
+        ])
+
+    def test_import_named_update(self):
+        cred = testcommon.DummyCredentials()
+        rules = xmlload(StringIO(import_named_xml))
+        rules.add(ImportSource('local_case_id', 'Id'))
+        src = datasrc.DataImpSrc('foo', StringIO(data))
+        imp = dataimp.DataImp(cred, 1, src, rules)
+        self.failIf(imp.errors, imp.errors)
+        self.assertEqual(imp.errors.count(), 0)
+        self.assertEqual(imp.new_cnt, 3)
+        self.assertEqual(imp.update_cnt, 0)
+        self.assertListEq(self._fetch_rows(), [
+            ('BLOGS', dt('2001-11-24'), 0, 'confirmed', 'NSW', None, '100',
+                1.0, 'Unknown', None, True, False, False), 
+            ('SMITH', age3y, 366, 'confirmed', 'NSW', None, '101',
+                2.0, 'Unknown', None, False, True, True), 
+            ('JONES', age4m, 31, 'preliminary', 'NSW', None, '102',
+                None, 'Unknown', None, False, False, False),
+        ])
+        src = datasrc.DataImpSrc('foo', StringIO(data + data_update))
+        imp = dataimp.DataImp(cred, 1, src, rules)
+        self.assertEqual(imp.new_cnt, 1)
+        self.assertEqual(imp.update_cnt, 4)
+        self.failIf(imp.errors, imp.errors)
+        self.assertEqual(imp.errors.count(), 0)
+        self.assertListEq(self._fetch_rows(), [
+            ('BLOGS', dt('2001-11-24'), 0, 'confirmed', 'NSW', None, '100',
+                1.0, 'Unknown', None, True, False, False), 
+            ('SMITH', dt('2000-01-20'), 0, 'confirmed', 'NSW', None, '101',
+                2.0, 'Unknown', None, False, False, False), 
+            ('JONES', age4m, 31, 'preliminary', 'NSW', None, '102',
+                None, 'Unknown', None, False, False, False),
+            ('WILLIAMS', dt('1940-12-02'), 0, 'excluded', 'NSW', None, '104', 
+                3.0, 'Unknown', None, False, True, False),
+        ])
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/dataimp/datasrc.py b/tests/dataimp/datasrc.py
new file mode 100644
index 0000000..8576d19
--- /dev/null
+++ b/tests/dataimp/datasrc.py
@@ -0,0 +1,113 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+import os
+import shutil
+import unittest
+from cStringIO import StringIO
+
+from casemgr.dataimp import datasrc
+from casemgr.dataimp.xmlload import xmlload
+
+import config
+
+data = '''\
+surname,case_status
+blogs,confirmed
+smith,confirmed
+jones,suspected
+'''
+
+named_rules = '''\
+<?xml version="1.0"?>
+<importrules name="test" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+</importrules>
+'''
+
+positional_rules = '''\
+<?xml version="1.0"?>
+<importrules name="test" mode="positional" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+</importrules>
+'''
+
+class DataSrcTest(unittest.TestCase):
+
+    scratchdir = os.path.join(os.path.dirname(__file__), 'scratch')
+
+    def setUp(self):
+        config.scratchdir = self.scratchdir
+        os.mkdir(config.scratchdir)
+
+    def tearDown(self):
+        shutil.rmtree(self.scratchdir)
+
+    def test_null_src(self):
+        self.failIf(bool(datasrc.NullDataImpSrc))
+        self.assertEqual(datasrc.NullDataImpSrc.preview.colvalues('x'), None)
+        self.assertEqual(datasrc.NullDataImpSrc.preview.colpreview('x'), [])
+        datasrc.NullDataImpSrc.release()
+        self.failIf(datasrc.DataImpSrc('foo', StringIO()))
+
+    def test_datasrc_named(self):
+        rules = xmlload(StringIO(named_rules))
+        f = StringIO(data)
+        src = datasrc.DataImpSrc('foo', f)
+        self.assertEqual(src.size, len(data))
+        # Preview
+        src.update_preview(rules)
+        self.assertEqual(src.preview.n_cols, 2)
+        self.assertEqual(src.preview.n_rows, 3)
+        self.assertEqual(src.preview.col_names, ['surname', 'case_status'])
+        self.assertEqual(len(src.preview.rows), 3)
+        self.assertEqual(src.preview.rows, [
+            ['blogs', 'confirmed'], 
+            ['smith', 'confirmed'], 
+            ['jones', 'suspected']])
+        self.assertEqual(src.preview.colvalues('case_status'), 
+                        ['confirmed', 'suspected'])
+        self.assertEqual(src.preview.colpreview('surname'), 
+                        ['blogs', 'smith', 'jones'])
+        self.assertEqual(src.preview.colpreview('case_status'), 
+                        ['confirmed', 'confirmed', 'suspected'])
+        src.release()
+
+    def test_datasrc_positional(self):
+        rules = xmlload(StringIO(named_rules))
+        rules.mode = 'positional'
+        f = StringIO(data)
+        src = datasrc.DataImpSrc('foo', f)
+        self.assertEqual(src.size, len(data))
+        # Preview
+        src.update_preview(rules)
+        self.assertEqual(src.preview.n_cols, 2)
+        self.assertEqual(src.preview.n_rows, 4)
+        self.assertEqual(src.preview.col_names, ['1', '2'])
+        self.assertEqual(len(src.preview.rows), 4)
+        self.assertEqual(src.preview.rows, [
+            ['surname', 'case_status'], 
+            ['blogs', 'confirmed'], 
+            ['smith', 'confirmed'], 
+            ['jones', 'suspected']])
+        self.assertEqual(src.preview.colvalues('2'), 
+                        ['case_status', 'confirmed', 'suspected'])
+        self.assertEqual(src.preview.colpreview('1'), 
+                        ['surname', 'blogs', 'smith', 'jones'])
+        self.assertEqual(src.preview.colpreview('2'), 
+                        ['case_status', 'confirmed', 'confirmed', 'suspected'])
+        src.release()
diff --git a/tests/dataimp/editor.py b/tests/dataimp/editor.py
new file mode 100644
index 0000000..7b9f8d8
--- /dev/null
+++ b/tests/dataimp/editor.py
@@ -0,0 +1,253 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+import unittest
+from cStringIO import StringIO
+
+from tests import testcommon
+
+from casemgr.dataimp import editor
+#from casemgr.dataimp.elements import *
+
+
+edit_new_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore" />
+'''
+
+edit_demog_field_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="given_names" src="None" />
+</importrules>
+'''
+
+edit_form_field_xml = '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="None" />
+ </form>
+</importrules>
+'''
+
+edit_xmas = '''\
+<?xml version="1.0"?>
+<importrules name="foo" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <agesource field="DOB" src="age" />
+ <source field="case_status" src="status">
+  <translate match="confirm" to="positive" ignorecase="yes" />
+  <regexp match="un.*" to="negative" />
+ </source>
+ <source field="given_names" src="first_name" />
+ <ignore field="surname" />
+ <fixed field="tags" value="fred" />
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="duration" />
+ </form>
+</importrules>
+'''
+
+class DataImpEditorTest(testcommon.AppTestCase):
+
+    create_tables = testcommon.AppTestCase.create_tables + (
+        'syndrome_case_status',
+        'import_defs',
+    )
+
+    def test_new(self):
+        e = editor.new(1)
+        self.assertEqual(e.rules_xml(), edit_new_xml)
+        self.failIf(e.has_changed())
+        v = e.view()
+        self.assertEqual(v.add_options,
+            [('', 'Choose...'), ('sars_exposure', 'Exposure History (SARS)')])
+        self.assertEqual(v.unused_cols, [])
+        self.assertEqual(len(v), 1)
+        self.assertEqual(v[0].name, '')
+        self.assertEqual(v[0].label, 'Demographic fields')
+        self.assertEqual(v[0], [])
+
+    def test_edit_field(self):
+        e = editor.new(1)
+        e.add_field('.given_names')
+        self.failUnless(e.has_changed())
+        self.assertEqual(e.rules_xml(), edit_demog_field_xml)
+        # View
+        v = e.view()
+        self.assertEqual(len(v[0]), 1)
+        self.assertEqual(v[0][0].name, 'given_names')
+        self.assertEqual(v[0][0].action_desc, 'source column None')
+
+        # Edit
+        f = e.edit_field('', 'given_names')
+        # action:source
+        self.assertEqual(f.selected.action_name, 'source')
+        self.assertEqual(f.selected.src, None)
+        f.selected.src = 'given_names'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="given_names" src="given_names" />
+</importrules>
+''')
+        # action:fixed
+        f.set_action('fixed')
+        f.selected.value = 'fred'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <fixed field="given_names" value="fred" />
+</importrules>
+''')
+        # action:ignore
+        f.set_action('ignore')
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <ignore field="given_names" />
+</importrules>
+''')
+        # action:agesource
+        e = editor.new(1)
+        e.add_field('.DOB')
+        f = e.edit_field('', 'DOB')
+        self.assertEqual(f.selected.action_name, 'source')
+        f.set_action('agesource')
+        f.selected.src = 'dob'
+        f.selected.src = 'age'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <agesource field="DOB" src="age" />
+</importrules>
+''')
+        # translation
+        e = editor.new(1)
+        e.add_field('.case_status')
+        f = e.edit_field('', 'case_status')
+        self.assertEqual(f.selected.action_name, 'source')
+        f.selected.src = 'status'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="case_status" src="status" />
+</importrules>
+''')
+        self.assertEqual(f.translations, [])
+        f.add_translate(regexp=False)
+        f.add_translate(regexp=True)
+        self.assertEqual(len(f.translations), 2)
+        f.translations[0].match = 'confirm'
+        f.translations[0].to = 'positive'
+        f.translations[0].ignorecase = 'True'
+        f.translations[1].match = 'un.*'
+        f.translations[1].to = 'negative'
+        f.translations[1].ignorecase = 'False'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <source field="case_status" src="status">
+  <translate match="confirm" to="positive" ignorecase="yes" />
+  <regexp match="un.*" to="negative" />
+ </source>
+</importrules>
+''')
+
+    def test_add_form(self):
+        e = editor.new(1)
+        e.add_form('sars_exposure')
+        v = e.view()
+        self.assertEqual(v.add_options, [])
+        self.assertEqual(len(v), 2)
+        self.assertEqual(v[1].name, 'sars_exposure')
+        self.assertEqual(v[1].label, 'Exposure History (SARS)')
+        self.assertEqual(v[1].add_options, [
+            ('', 'Choose...'),
+            ('sars_exposure.Contact_duration', 'Contact duration (hours)'),
+            ('sars_exposure.Close_contact', 'Contact with case'),
+            ('sars_exposure.Contact_date_first', 'Date of first contact'),
+            ('sars_exposure.Contact_favourite_food', 'Favourite foods'),
+            ('sars_exposure.Contact_date_last', 'Most recent contact')
+        ])
+        e.add_field('sars_exposure.Contact_duration')
+        self.failUnless(e.has_changed())
+        self.assertEqual(e.rules_xml(), edit_form_field_xml)
+        v = e.view()
+        self.assertEqual(v[0], [])
+        self.assertEqual(len(v[1]), 1)
+        self.assertEqual(v[1][0].name, 'Contact_duration')
+        self.assertEqual(v[1][0].action_desc, 'source column None')
+
+        # Delete form
+        e.del_form('sars_exposure')
+        self.assertEqual(e.rules_xml(), edit_new_xml)
+
+        # Edit form
+        e = editor.new(1)
+        e.add_form('sars_exposure')
+        e.add_field('sars_exposure.Contact_duration')
+        self.assertEqual(e.rules_xml(), edit_form_field_xml)
+        f = e.edit_field('sars_exposure', 'Contact_duration')
+        self.assertEqual(f.selected.action_name, 'source')
+        self.assertEqual(f.selected.src, None)
+        f.selected.src = 'duration'
+        e.save_edit_field(f)
+        self.assertEqual(e.rules_xml(), '''\
+<?xml version="1.0"?>
+<importrules name="" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <form name="sars_exposure" version="1">
+  <source field="Contact_duration" src="duration" />
+ </form>
+</importrules>
+''')
+
+    def test_round_trip(self):
+        # Test in-memory round-trip
+        f = StringIO(edit_xmas)
+        e = editor.load_file(None, 1, None, f)
+        self.assertEqual(e.rules_xml(), edit_xmas)
+        # Now test DB round-trip
+        e.save()
+        self.assertEqual(e.def_id, 1)
+        e = editor.load(None, 1, 1)
+        self.assertEqual(e.rules_xml(), edit_xmas)
+        self.assertEqual(e.available(), [(1, 'foo')])
+        # Resave
+        e.save()
+        self.assertEqual(e.available(), [(1, 'foo')])
+        # Duplicate name
+        f = StringIO(edit_xmas)
+        e = editor.load_file(None, 1, None, f)
+        self.assertRaises(editor.Error, e.save)
+        # Check delete def
+        e = editor.load(None, 1, 1)
+        e.delete()
+        self.assertEqual(e.available(), [])
+        self.assertRaises(editor.Error, editor.load, None, 1, 1)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/dataimp/xmlsaveload.py b/tests/dataimp/xmlsaveload.py
new file mode 100644
index 0000000..c751d13
--- /dev/null
+++ b/tests/dataimp/xmlsaveload.py
@@ -0,0 +1,79 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
+
+import unittest
+from cStringIO import StringIO
+
+from tests import testcommon
+
+from casemgr.dataimp import editor
+from casemgr.dataimp.elements import *
+from casemgr.dataimp.xmlsave import xmlsave
+from casemgr.dataimp.xmlload import xmlload
+
+simple_rules = '''\
+<?xml version="1.0"?>
+<importrules name="test" mode="named" encoding="utf-8" fieldsep="," srclabel="import" conflicts="ignore">
+ <agesource field="DOB" src="date-of-birth" age="age">
+  <date format="YYYY/MM/DD" />
+ </agesource>
+ <ignore field="case_definition" />
+ <fixed field="case_status" value="None" />
+ <source field="given_names" src="firstname">
+  <case mode="title" />
+ </source>
+ <form name="foo" version="1">
+  <fixed field="bah" value="baz" />
+  <multivalue field="checkbox" src="multichoice" delimiter="/">
+   <case mode="upper" />
+   <translate match="PROVISIONAL" to="SUSPECTED" />
+   <regexp match="CONFIRMED_.*" to="CONFIRMED" />
+  </multivalue>
+ </form>
+</importrules>
+'''
+
+class DataImpSaveNLoadTest(testcommon.TestCase):
+
+    def test_save(self):
+        rules = ImportRules('test')
+        rule = ImportSource('given_names', 'firstname')
+        rule.translations.append(Case('title'))
+        rules.add(rule)
+        rule = ImportAgeSource('DOB', 'date-of-birth', 'age')
+        rule.translations.append(Date(format='YYYY/MM/DD'))
+        rules.add(rule)
+        rules.add(ImportFixed('case_status', 'None'))
+        rules.add(ImportIgnore('case_definition'))
+        form = rules.new_form('foo', 1)
+        form.add(ImportFixed('bah', 'baz'))
+        rule = ImportMultiValue('checkbox', 'multichoice', '/')
+        rule.translations.append(Case('upper'))
+        rule.translations.append(Translate('PROVISIONAL', 'SUSPECTED'))
+        rule.translations.append(RegExp('CONFIRMED_.*', 'CONFIRMED'))
+        form.add(rule)
+        f = StringIO()
+        xmlsave(f, rules)
+        self.assertEqLines(f.getvalue(), simple_rules)
+
+    def test_load(self):
+        rules = xmlload(StringIO(simple_rules))
+        f = StringIO()
+        xmlsave(f, rules)
+        self.assertEqual(f.getvalue(), simple_rules)
diff --git a/tests/datetimetest.py b/tests/datetimetest.py
new file mode 100644
index 0000000..f111920
--- /dev/null
+++ b/tests/datetimetest.py
@@ -0,0 +1,262 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+from cocklebur import datetime
+from mx import DateTime
+import operator
+
+class Case(unittest.TestCase):
+
+    def tearDown(self):
+        datetime.set_date_style('DMY')
+
+    def _test(self, fn, args, expect):
+        result = fn(*args)
+        self.assertEqual(result, expect, 
+                         'input %s, expected %s, got %s' % \
+                            (', '.join([str(a) for a in args]), expect, result))
+
+    def parse_date(self):
+        def _parse_date(arg, expect):
+            self._test(datetime.parse_date, (arg,), expect)
+
+        _parse_date('11/12/30', (1930, 12, 11))
+        _parse_date('11/12/07', (2007, 12, 11))
+        _parse_date('11/12/00', (2000, 12, 11))
+        _parse_date('11/12/2000', (2000, 12, 11))
+        _parse_date('11-12-00', (2000, 12, 11))
+        _parse_date('11-12-2000', (2000, 12, 11))
+        _parse_date('11-12-1900', (1900, 12, 11))
+        _parse_date('1900-12-11', (1900, 12, 11))
+        _parse_date('2007-12-11', (2007, 12, 11))
+        self.assertRaises(datetime.Error, 
+                          datetime.parse_date, '11/12')
+        self.assertRaises(datetime.Error, 
+                          datetime.parse_date, '11/12/30/03')
+        self.assertEqual(datetime.set_date_style('MDY'), 'DMY')
+        _parse_date('12/11/30', (1930, 12, 11))
+        _parse_date('12-11-2000', (2000, 12, 11))
+        _parse_date('2007-12-11', (2007, 12, 11))
+
+    def parse_time(self):
+        def _parse_time(arg, expect):
+            self._test(datetime.parse_time, (arg,), expect)
+        
+        _parse_time('11:12', (11,12,0))
+        _parse_time('11:12:13', (11,12,13))
+        _parse_time('11:12:13', (11,12,13))
+        self.assertRaises(datetime.Error,
+                          datetime.parse_time, '11:12:13:14')
+        self.assertRaises(datetime.Error,
+                          datetime.parse_time, '-11:12:13')
+        self.assertRaises(datetime.Error,
+                          datetime.parse_time, '11:-12:13')
+        self.assertRaises(datetime.Error,
+                          datetime.parse_time, '24:00:00')
+
+    def mx_parse_date(self):
+        def _mx_parse_date(arg, expect):
+            self._test(datetime.mx_parse_date, (arg,), expect)
+        
+        _mx_parse_date('11/12/30', DateTime.DateTime(1930, 12, 11))
+        _mx_parse_date('11/12/30', DateTime.DateTime(1930, 12, 11, 11, 59, 59))
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_date, '30/02/03')
+
+    def mx_parse_time(self):
+        def _mx_parse_time(arg, expect):
+            self._test(datetime.mx_parse_time, (arg,), expect)
+        
+        _mx_parse_time('11:12', DateTime.DateTimeDelta(0, 11, 12))
+        _mx_parse_time('11:12:13', DateTime.DateTimeDelta(0, 11, 12, 13))
+        _mx_parse_time('11:12:13', DateTime.DateTimeDelta(0, 11, 12, 13.999))
+        _mx_parse_time(DateTime.DateTimeDelta(0, 11, 12, 13.999),
+                       DateTime.DateTimeDelta(0, 11, 12, 13.999))
+        _mx_parse_time(DateTime.DateTime(2003, 02, 27, 11, 12, 13),
+                       DateTime.DateTimeDelta(0, 11, 12, 13))
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_time, '11:22:33:44')
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_time,
+                          DateTime.DateTimeDelta(0, 24, 0, 0))
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_time,
+                          DateTime.DateTimeDelta(1, 0, 0, 0))
+
+    def mx_parse_datetime(self):
+        def _mx_parse_datetime(arg, expect):
+            self._test(datetime.mx_parse_datetime, (arg,), expect)
+        
+        _mx_parse_datetime('27/02/03 11:12:13', 
+                           DateTime.DateTime(2003, 02, 27, 11, 12, 13))
+        _mx_parse_datetime('11:12:13 27/02/03', 
+                           DateTime.DateTime(2003, 02, 27, 11, 12, 13))
+        _mx_parse_datetime('11:12:13 27/02/03', 
+                           DateTime.DateTime(2003, 02, 27, 11, 12, 13.999))
+        t = datetime.mx_parse_datetime('11:12:13 27/02/03')
+        self._test(t.time, (), DateTime.DateTimeDelta(0, 11, 12, 13))
+        self._test(t.date, (), DateTime.DateTime(2003, 02, 27))
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_datetime, '11:12:13pm 27/02/03')
+        self.assertRaises(datetime.Error,
+                          datetime.mx_parse_datetime, '11:12:13 pm 27/02/03')
+
+    def operators(self):
+        def _test_op(a, op, b, expect):
+            if a is None:
+                aa = None
+            else:
+                aa = datetime.mx_parse_datetime(a)
+            if b is None:
+                bb = None
+            elif type(b) is DateTime.DateTimeDeltaType:
+                bb = b
+            else:
+                bb = datetime.mx_parse_datetime(b)
+            r = op(aa, bb)
+            self.assertEqual(r, expect, 
+                             '%s %s %s, expected %s, got %s' %\
+                                (a, op.__name__, b, expect, r))
+
+        _test_op('1/1/03', operator.eq, '1/1/03', True)
+        _test_op('1/1/03', operator.eq, '2/1/03', False)
+        _test_op('1/1/03', operator.eq, None, False)
+
+        _test_op('1/1/03', operator.ne, '1/1/03', False)
+        _test_op('1/1/03', operator.ne, '2/1/03', True)
+        _test_op('1/1/03', operator.ne, None, True)
+
+        _test_op('1/1/03', operator.gt, '2/1/03', False)
+        _test_op('2/1/03', operator.gt, '1/1/03', True)
+        _test_op('2/1/03', operator.gt, None, True)
+        _test_op('2/1/03', operator.lt, None, False)
+        delta = DateTime.DateTimeDelta(1)
+        _test_op('2/1/03', operator.add, delta,
+                 DateTime.DateTime(2003,1,3))
+        _test_op('2/1/03', operator.sub, delta,
+                 DateTime.DateTime(2003,1,1))
+
+    def relative(self):
+        def _test(a, b, expect):
+            a = datetime.mx_parse_datetime(a)
+            b = datetime.mx_parse_datetime(b)
+            got = datetime.relative(a, b)
+            self.assertEqual(got, expect, 'got %s, expected %s' % (got, expect))
+        _test('1/1/03 12:34:56', '1/1/03 12:34:56', 'in less than a minute')
+        _test('1/1/03 12:33:56', '1/1/03 12:34:56', '1 minute ago')
+        _test('1/1/03 12:35:56', '1/1/03 12:34:56', 'in 1 minute')
+        _test('1/1/03 12:36:56', '1/1/03 12:34:56', 'in 2 minutes')
+        _test('1/1/03 14:34:56', '1/1/03 12:34:56', 'in 2 hours')
+        _test('3/1/03 12:34:56', '1/1/03 12:34:56', 'in 2 days')
+        _test('15/1/03 12:34:56', '1/1/03 12:34:56', 'in 2 weeks')
+        _test('1/3/03 12:34:56', '1/1/03 12:34:56', 'in 2 months')
+        _test('1/1/05 12:34:56', '1/1/03 12:34:56', 'in 2 years')
+
+    def parse_discrete(self):
+        def _test(rel, ref, expect):
+            ref = datetime.mx_parse_datetime(ref)
+            expect = datetime.mx_parse_datetime(expect)
+            got = datetime.parse_discrete(rel, ref)
+            self.assertEqual(got, expect, 'got %s, expected %s' % (got, expect))
+        _test('now', '1/1/03 12:34:56', '1/1/03 12:34:56')
+        _test('tomorrow', '1/1/03 12:34:56', '2/1/03 07:00:00')
+        _test('yesterday', '1/1/03 12:34:56', '31/12/02 07:00:00')
+        _test('week', '1/1/03 12:34:56', '8/1/03 07:00:00')
+        _test('monday', '1/1/03 12:34:56', '6/1/03 07:00:00')
+        _test('tuesday', '1/1/03 12:34:56', '7/1/03 07:00:00')
+        _test('wednesday', '1/1/03 12:34:56', '8/1/03 07:00:00')
+        _test('thursday', '1/1/03 12:34:56', '2/1/03 07:00:00')
+        _test('friday', '1/1/03 12:34:56', '3/1/03 07:00:00')
+        _test('saturday', '1/1/03 12:34:56', '4/1/03 07:00:00')
+        _test('sunday', '1/1/03 12:34:56', '5/1/03 07:00:00')
+        # Hours
+        _test('1h', '1/1/03 12:34:56', '1/1/03 13:34:56.00')
+        _test('hour', '1/1/03 12:34:56', '1/1/03 13:34:56.00')
+        _test('1 hour', '1/1/03 12:34:56', '1/1/03 13:34:56.00')
+        _test('one hour', '1/1/03 12:34:56', '1/1/03 13:34:56.00')
+        _test('2 hours', '1/1/03 12:34:56', '1/1/03 14:34:56.00')
+        _test('two hours', '1/1/03 12:34:56', '1/1/03 14:34:56.00')
+        _test('24 hours', '1/1/03 12:34:56', '2/1/03 12:34:56.00')
+        _test('3/24', '1/1/03 12:34:56', '1/1/03 15:34:56.00')
+        _test('three hours', '1/1/03 12:34:56', '1/1/03 15:34:56.00')
+        _test('four hours', '1/1/03 12:34:56', '1/1/03 16:34:56.00')
+        _test('five hours', '1/1/03 12:34:56', '1/1/03 17:34:56.00')
+        _test('six hours', '1/1/03 12:34:56', '1/1/03 18:34:56.00')
+        _test('seven hours', '1/1/03 12:34:56', '1/1/03 19:34:56.00')
+        _test('eight hours', '1/1/03 12:34:56', '1/1/03 20:34:56.00')
+        _test('nine hours', '1/1/03 12:34:56', '1/1/03 21:34:56.00')
+        _test('ten hours', '1/1/03 12:34:56', '1/1/03 22:34:56.00')
+        _test('2 hours ago', '1/1/03 12:34:56', '1/1/03 10:34:56.00')
+        _test('two hours ago', '1/1/03 12:34:56', '1/1/03 10:34:56.00')
+        # Days
+        _test('1d', '1/1/03 12:34:56', '2/1/03 12:34:56.00')
+        _test('day', '1/1/03 12:34:56', '2/1/03 12:34:56.00')
+        _test('2 days', '1/1/03 12:34:56', '3/1/03 12:34:56.00')
+        _test('3/7', '1/1/03 12:34:56', '4/1/03 12:34:56.00')
+        _test('2 days ago', '1/1/03 12:34:56', '30/12/02 12:34:56.00')
+        # Weeks
+        _test('1w', '1/1/03 12:34:56', '8/1/03 07:00:00')
+        _test('week', '1/1/03 12:34:56', '8/1/03 07:00:00')
+        _test('2 weeks', '1/1/03 12:34:56', '15/1/03 07:00:00')
+        _test('3/52', '1/1/03 12:34:56', '22/1/03 07:00:00')
+        _test('12/52', '1/1/03 12:34:56', '26/3/03 07:00:00')
+        # Fortnight
+        _test('fortnight', '1/1/03 12:34:56', '15/1/03 07:00:00')
+        # Months
+        _test('1m', '1/1/03 12:34:56', '1/2/03 07:00:00')
+        _test('2 months', '1/1/03 12:34:56', '1/3/03 07:00:00')
+        _test('3/12', '1/1/03 12:34:56', '1/4/03 07:00:00')
+        # Years
+        _test('1y', '1/1/03 12:34:56', '1/1/04 07:00:00')
+        _test('1 year', '1/1/03 12:34:56', '1/1/04 07:00:00')
+        _test('1 yr', '1/1/03 12:34:56', '1/1/04 07:00:00')
+        _test('2y', '1/1/03 12:34:56', '1/1/05 07:00:00')
+        _test('2 yrs', '1/1/03 12:34:56', '1/1/05 07:00:00')
+        _test('two yrs', '1/1/03 12:34:56', '1/1/05 07:00:00')
+        _test('two years', '1/1/03 12:34:56', '1/1/05 07:00:00')
+        _test('1 year ago', '1/1/03 12:34:56', '1/1/02 07:00:00')
+        _test('two years ago', '1/1/03 12:34:56', '1/1/01 07:00:00')
+        # Error handling
+        self.assertRaises(datetime.Error, datetime.parse_discrete,
+                          'sdfkjhgksjfdg')
+        self.assertRaises(datetime.Error, datetime.parse_discrete,
+                          'one two three')
+        self.assertRaises(datetime.Error, datetime.parse_discrete,
+                          'one ago')
+        self.assertRaises(datetime.Error, datetime.parse_discrete,
+                          'now days ago')
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'parse_date',
+        'parse_time',
+        'mx_parse_date',
+        'mx_parse_time',
+        'mx_parse_datetime',
+        'operators',
+        'relative',
+        'parse_discrete',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Case, self.test_list))
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/tests/dbobj/__init__.py b/tests/dbobj/__init__.py
new file mode 100644
index 0000000..9f9d37e
--- /dev/null
+++ b/tests/dbobj/__init__.py
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+
+class AllTestSuite(unittest.TestSuite):
+    # add modules with tests here
+    all_tests = [
+        'query_builder',
+        'result',
+        'participation_table',
+    ]
+    def __init__(self):
+        unittest.TestSuite.__init__(self)
+        for module_name in self.all_tests:
+            module = __import__(module_name, globals())
+            self.addTest(module.suite())
+
+def suite():
+    return AllTestSuite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='AllTestSuite')
diff --git a/tests/dbobj/participation_table.py b/tests/dbobj/participation_table.py
new file mode 100644
index 0000000..734cd45
--- /dev/null
+++ b/tests/dbobj/participation_table.py
@@ -0,0 +1,243 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import unittest
+
+from tests import testcommon
+
+from cocklebur import dbobj
+from cocklebur.dbobj.participation_table import ParticipationTable
+
+class DatabaseDescriber(dbobj.DatabaseDescriberCore):
+    oidValue = 999
+    rowcount = 1
+
+    def __init__(self):
+        dbobj.DatabaseDescriberCore.__init__(self, testcommon.test_dsn)
+        self.results = []
+
+    def set_result(self, table, rows, expect=None, *expect_args):
+        self.results.append((table, rows, expect, expect_args))
+
+    def cursor(self):
+        return self
+
+    def execute(self, cmd, args):
+#        print cmd, args
+        try:
+            table, self.rows, expect, expect_args = self.results.pop(0)
+        except IndexError:
+            expect, expect_args = '<nothing>', ()
+        if expect is not None:
+            try: cmd = cmd % tuple(args)
+            except TypeError: pass
+            try: expect = expect % tuple(expect_args)
+            except TypeError: pass
+            if cmd != expect:
+                raise AssertionError('execute expected:\n    %s\ngot:\n    %s'% 
+                                        (expect, cmd))
+        self.description = []
+        if table:
+            for col in self.get_table(table).get_columns():
+                self.description.append((col.name,))
+
+    def fetchmany(self, n):
+        result = self.rows[:n]
+        del self.rows[:n]
+        return result
+
+class Case(unittest.TestCase):
+
+    def pt_test(self):
+        def rows_column(rows, col):
+            return [getattr(row, col) for row in rows]
+        db = DatabaseDescriber()
+
+        # Set up table describers
+        td = db.new_table('left_table')
+        td.column('left_key', dbobj.SerialColumn, primary_key=True)
+
+        td = db.new_table('right_table')
+        td.column('right_key', dbobj.SerialColumn, primary_key=True)
+        td.column('right_name', dbobj.StringColumn)
+
+        td = db.new_table('pt_table')
+        td.column('pt_key', dbobj.SerialColumn, primary_key=True)
+        td.column('left', dbobj.ReferenceColumn, references='left_table')
+        td.column('right', dbobj.ReferenceColumn, references='right_table')
+
+        # Load participation table
+        pt = ParticipationTable(td, 'left', 'right')
+        db.set_result('pt_table', [[1, 1, 1], [2, 1, 2]],
+                      'SELECT pt_table.* FROM pt_table'
+                        ' WHERE (left IN (%s,%s)) ORDER BY pt_key', 1, 2)
+        db.set_result('right_table', [[1, 'a'], [2, 'b']],
+                      'SELECT right_table.* FROM right_table'
+                       ' WHERE (right_key IN (%s,%s))', 1, 2)
+        pt.preload([1, 2])
+
+        # Check initial state
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertRaises(KeyError, pt.__getitem__, 0)
+        self.assertEqual(len(pt[1]), 2)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b'])
+        self.assertEqual(len(pt[2]), 0)
+        self.assertEqual(list(pt[2]), [])
+
+        # Revert should do nothing at this stage
+        pt[1].db_revert()
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b'])
+
+        # Moving the first item up or the last item down should be NOOP
+        pt[1].move_up(0)
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        pt[1].move_down(1)
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+
+        # Test move up
+        pt[1].move_up(1)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [2, 1])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [2, 1])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['b', 'a'])
+
+        # Test move down
+        pt[1].move_down(0)
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b'])
+
+        # Test pop (delete)
+        a = pt[1].pop(0)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['b'])
+
+        b = pt[1].pop(0)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [])
+        self.assertEqual(rows_column(pt[1], 'right_name'), [])
+
+        pt[1].add(a)
+        pt[1].add(b)
+        self.assertEqual(pt.db_has_changed(), True)     # XXX
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b'])
+
+        # Test revert after deletes and adds
+        pt.db_revert()
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(pt[1].slave_keys(), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b'])
+
+        # Add a new row
+        row = db.new_row('right_table')
+        row.right_key = 3
+        row.right_name = 'c'
+        self.assertEqual(row in pt[1], False)
+        pt[1].add(row)
+        self.assertEqual(row in pt[1], True)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [1, 2, 3])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [1, 2, 3])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['a', 'b', 'c'])
+        db.set_result(None, [],
+                      'INSERT INTO pt_table (left,right) VALUES (%s,%s)', 1, 3)
+        db.set_result('pt_table', [[3, 1, 3]],
+                      'SELECT * FROM pt_table WHERE oid=%s', 999)
+        pt.db_update()
+        self.assertEqual(pt.db_has_changed(), False)
+
+        # Revert now should have no effect
+        pt.db_revert()
+        self.assertEqual(pt[1].slave_keys(), [1, 2, 3])
+
+        # Test pop with commit
+        pt[1].pop(0)
+        db.set_result(None, [],
+                      'DELETE FROM pt_table WHERE pt_key=%s', 1)
+        pt.db_update()
+        self.assertEqual(pt.db_has_changed(), False)
+
+        # Move with commit
+        pt[1].move_up(1)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [3, 2])
+        self.assertEqual(rows_column(pt[1], 'right_key'), [3, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['c', 'b'])
+        db.set_result(None, [],
+                      'UPDATE pt_table SET right=%s WHERE pt_key=%s', 3, 2)
+        db.set_result('pt_table', [[2, 1, 3]],
+                      'SELECT * FROM pt_table WHERE pt_key=%s', 2)
+        db.set_result(None, [],
+                      'UPDATE pt_table SET right=%s WHERE pt_key=%s', 2, 3)
+        db.set_result('pt_table', [[3, 1, 2]],
+                      'SELECT * FROM pt_table WHERE pt_key=%s', 3)
+        pt.db_update()
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(rows_column(pt[1], 'right_key'), [3, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['c', 'b'])
+
+        # Add row, move, commit
+        row = db.new_row('right_table')
+        row.right_key = 4
+        row.right_name = 'd'
+        self.assertEqual(row in pt[1], False)
+        pt[1].add(row)
+        self.assertEqual(pt.db_has_changed(), True)
+        self.assertEqual(pt[1].slave_keys(), [3, 2, 4])
+        pt[1].move_up(2)
+        self.assertEqual(pt[1].slave_keys(), [3, 4, 2])
+        db.set_result(None, [],
+                      'UPDATE pt_table SET right=%s WHERE pt_key=%s', 4, 3)
+        db.set_result('pt_table', [[3, 1, 4]],
+                      'SELECT * FROM pt_table WHERE pt_key=%s', 3)
+        db.set_result(None, [],
+                      'INSERT INTO pt_table (left,right) VALUES (%s,%s)', 1, 2)
+        db.set_result('pt_table', [[4, 1, 2]],
+                      'SELECT * FROM pt_table WHERE oid=%s', 999)
+        pt.db_update()
+        self.assertEqual(pt.db_has_changed(), False)
+        self.assertEqual(rows_column(pt[1], 'right_key'), [3, 4, 2])
+        self.assertEqual(rows_column(pt[1], 'right_name'), ['c', 'd', 'b'])
+
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'pt_test',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Case, self.test_list))
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/tests/dbobj/query_builder.py b/tests/dbobj/query_builder.py
new file mode 100644
index 0000000..f024a69
--- /dev/null
+++ b/tests/dbobj/query_builder.py
@@ -0,0 +1,392 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+from cocklebur.dbobj import query_builder
+
+class DummyColDesc:
+    def __init__(self, name):
+        self.name = name
+
+class DummyCurs:
+    def __init__(self, table_desc):
+        self.table_desc = table_desc
+        self.table_desc.execute_cmd = None
+        self.table_desc.execute_args = None
+
+    def execute(self, cmd, args):
+        self.table_desc.execute_cmd = cmd
+        self.table_desc.execute_args = tuple(args)
+
+    def fetchone(self):
+        return self.table_desc.fetch_result
+
+    def fetchall(self):
+        return self.table_desc.fetch_result
+
+    def close(self):
+        pass
+
+class DummyDB:
+    def __init__(self, table_desc):
+        self.table_desc = table_desc
+
+    def cursor(self):
+        return DummyCurs(self.table_desc)
+
+    def get_table(self, name):
+        return self.table_desc
+
+class DummyTableDesc:
+    name = 'test_table'
+    
+    def __init__(self, fetch_result=None):
+        self.fetch_result = fetch_result
+        self.db = DummyDB(self)
+
+    def get_primary_cols(self):
+        return DummyColDesc('pkey_a'), DummyColDesc('pkey_b')
+
+class Case(unittest.TestCase):
+    def _test(self, query, expect, expect_args = [], **kwargs):
+        got, got_args = query.build_expr(**kwargs)
+        self.assertEqual((expect, expect_args), (got, got_args),
+                         '\nExpected: %s (args %s)\nGot     : %s (args %s)' %\
+                                (expect, expect_args, got, got_args))
+
+    def _test_exec(self, table_desc, meth, args, kwargs, exec_cmd, 
+                   exec_args=[], expect_result=None):
+
+        result = meth(*args, **kwargs)
+        self.assertEqual((table_desc.execute_cmd, table_desc.execute_args), 
+                         (exec_cmd, exec_args),
+                         '\nExecute expected: %s (args %s)'
+                         '\nGot     : %s (args %s)' %
+                         (exec_cmd, exec_args, 
+                          table_desc.execute_cmd, table_desc.execute_args))
+        self.assertEqual(result, expect_result,
+                         'Returned: %r, expected %r' % 
+                         (result, expect_result))
+        
+
+    def test_expr_build(self):
+        expr_builder = query_builder.ExprBuilder(None, 'AND')
+        expr_builder.where('a = %s', 1)
+        expr_builder.where('(b = %s OR c = %s)', 2, 3)
+        self._test(expr_builder, '(a = %s AND (b = %s OR c = %s))', [1, 2, 3])
+
+    def test_simple(self):
+        query = query_builder.Query(DummyTableDesc())
+        self._test(query, 'SELECT test_table.* FROM test_table')
+
+    def test_where(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s)', [1])
+
+    def test_where_and(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s AND b = %s)', [1, 2])
+
+        query = query_builder.Query(DummyTableDesc(), conjunction = 'OR')
+        sub_expr = query.sub_expr()
+        sub_expr.where('a = %s', 1)
+        sub_expr.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE ((a = %s AND b = %s))', [1, 2])
+
+    def test_where_or(self):
+        query = query_builder.Query(DummyTableDesc(), conjunction = 'OR')
+        query.where('a = %s', 1)
+        query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s OR b = %s)', [1, 2])
+
+        query = query_builder.Query(DummyTableDesc())
+        sub_expr = query.sub_expr()
+        sub_expr.where('a = %s', 1)
+        sub_expr.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE ((a = %s OR b = %s))', [1, 2])
+
+    def test_where_andor(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        sub_expr = query.sub_expr()
+        sub_expr.where('b = %s', 2)
+        query.where('d = %s', 4)
+        sub_expr.where('c = %s', 3)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s AND (b = %s OR c = %s) AND d = %s)', 
+                   [1, 2, 3, 4])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        sub_expr = query.sub_expr()
+        query.where('d = %s', 4)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s AND d = %s)', 
+                   [1, 4])
+
+
+    def test_where_not(self):
+        query = query_builder.Query(DummyTableDesc(), negate=True)
+        query.where('a = %s', 1)
+        query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE NOT (a = %s AND b = %s)', 
+                   [1, 2])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        notq = query.sub_expr(negate=True, conjunction='AND')
+        notq.where('b = %s', 2)
+        notq.where('c = %s', 3)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s AND NOT (b = %s AND c = %s))', 
+                   [1, 2, 3])
+
+    def test_where_in(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.where_in('a', [])
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (false)', 
+                   [])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where_in('a', [1])
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a IN (%s))', 
+                   [1])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where_in('a', [1, 2])
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a IN (%s,%s))', 
+                   [1,2])
+
+    def test_join(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        query.join('JOIN foo USING (a)')
+        query.join('LEFT JOIN bah USING (b)')
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' JOIN foo USING (a)'
+                          ' LEFT JOIN bah USING (b)'
+                          ' WHERE (a = %s)', [1])
+
+    def test_distinct(self):
+        query = query_builder.Query(DummyTableDesc(), distinct = True)
+        self._test(query, 'SELECT DISTINCT test_table.* FROM test_table')
+
+    def test_order_by(self):
+        query = query_builder.Query(DummyTableDesc(), order_by = 'a')
+        self._test(query, 'SELECT test_table.* FROM test_table ORDER BY a')
+        query = query_builder.Query(DummyTableDesc(), order_by = ('a', 'b'))
+        self._test(query, 'SELECT test_table.* FROM test_table ORDER BY a, b')
+
+    def test_for_update(self):
+        query = query_builder.Query(DummyTableDesc(), for_update = True)
+        self._test(query, 'SELECT test_table.* FROM test_table FOR UPDATE')
+
+    def test_limit(self):
+        query = query_builder.Query(DummyTableDesc(), limit = 100)
+        self._test(query, 'SELECT test_table.* FROM test_table LIMIT 100')
+
+    def test_keys_only(self):
+        query = query_builder.Query(DummyTableDesc())
+        self._test(query, 'SELECT test_table.pkey_a, test_table.pkey_b'
+                          ' FROM test_table',
+                   [], columns = ['test_table.pkey_a', 'test_table.pkey_b'])
+
+    def test_sub_select(self):
+        query = query_builder.Query(DummyTableDesc())
+        subquery = query.sub_select()
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM (SELECT test_table.* FROM test_table)'
+                          ' AS test_table')
+
+    def test_sub_select_where(self):
+        query = query_builder.Query(DummyTableDesc())
+        subquery = query.sub_select()
+        subquery.where('b = %s', 2)
+        query.where('a = %s', 1)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM (SELECT test_table.*'
+                                  ' FROM test_table'
+                                  ' WHERE (b = %s))'
+                          ' AS test_table'
+                          ' WHERE (a = %s)', [2, 1])
+
+    def test_in_select(self):
+        query = query_builder.Query(DummyTableDesc())
+        inquery = query.in_select('d', 'test_table')
+        inquery.where('a = %s', 1)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE ('
+                           'd IN'
+                           ' (SELECT d'
+                             ' FROM test_table'
+                             ' WHERE (a = %s)))', [1])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        inquery = query.in_select('d', 'test_table', op='NOT IN', columns=['e'])
+        inquery.where('b = %s', 2)
+        query.where('c = %s', 3)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE ('
+                           'a = %s'
+                           ' AND d NOT IN'
+                           ' (SELECT e'
+                             ' FROM test_table'
+                             ' WHERE (b = %s))'
+                           ' AND c = %s)', [1, 2, 3])
+
+    def test_set_op(self):
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        union_query = query.union_query()
+        union_query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s)'
+                         ' UNION'
+                          ' SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (b = %s)', [1, 2])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        intersect_query = query.intersect_query()
+        intersect_query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s)'
+                         ' INTERSECT'
+                          ' SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (b = %s)', [1, 2])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        except_query = query.except_query()
+        except_query.where('b = %s', 2)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s)'
+                         ' EXCEPT'
+                          ' SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (b = %s)', [1, 2])
+
+        query = query_builder.Query(DummyTableDesc())
+        query.where('a = %s', 1)
+        except_query = query.except_query()
+        except_query.where('c = %s', 3)
+        query.where('b = %s', 2)
+        except_query.where('d = %s', 4)
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (a = %s'
+                            ' AND b = %s)'
+                         ' EXCEPT'
+                          ' SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE (c = %s'
+                            ' AND d = %s)', [1, 2, 3, 4])
+
+    def test_where_pkey(self):
+        query = query_builder.Query(DummyTableDesc())
+        query.by_primary_keys([(1, 2), (3, 4), (5, 6)])
+        self._test(query, 'SELECT test_table.*'
+                          ' FROM test_table'
+                          ' WHERE ((pkey_a,pkey_b) IN ((%s,%s),(%s,%s),(%s,%s)))',
+                   [1, 2, 3, 4, 5, 6])
+
+    def test_fetchall(self):
+        table_desc = DummyTableDesc()
+        query = query_builder.Query(table_desc)
+        self._test_exec(table_desc, query.delete, (), {},
+                        'DELETE FROM test_table', (), None)
+        query.where('a = %s', 1)
+        self._test_exec(table_desc, query.delete, (), {},
+                        'DELETE FROM test_table WHERE (a = %s)', (1,), 
+                        None)
+
+    def test_aggregate(self):
+        table_desc = DummyTableDesc(fetch_result=[10])
+        query = query_builder.Query(table_desc)
+        self._test_exec(table_desc, query.aggregate, ('COUNT(*)',), {},
+                        'SELECT COUNT(*) FROM test_table', (), 10)
+        query.where('a = %s', 1)
+        self._test_exec(table_desc, query.aggregate, ('COUNT(*)',), {},
+                        'SELECT COUNT(*) FROM test_table WHERE (a = %s)', 
+                        (1,), 10)
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'test_expr_build',
+        'test_simple',
+        'test_where',
+        'test_where_and',
+        'test_where_or',
+        'test_where_andor',
+        'test_where_not',
+        'test_where_in',
+        'test_join',
+        'test_distinct',
+        'test_order_by',
+        'test_for_update',
+        'test_limit',
+        'test_keys_only',
+        'test_sub_select',
+        'test_sub_select_where',
+        'test_in_select',
+        'test_set_op',
+        'test_where_pkey',
+        'test_fetchall',
+        'test_aggregate',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Case, self.test_list))
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/dbobj/result.py b/tests/dbobj/result.py
new file mode 100644
index 0000000..fc25318
--- /dev/null
+++ b/tests/dbobj/result.py
@@ -0,0 +1,353 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+from cPickle import dumps, loads
+from cocklebur import dbobj, datetime
+from mx.DateTime import DateTime
+
+from tests import testcommon
+
+class DummyCursor:
+    description = [
+        ('textcol',),
+        ('intcol',),
+        ('boolcol',),
+        ('datecol',),
+        ('lastupt',),
+        ('id',),
+        ('ignore',),
+    ]
+    oidValue = 999
+    rowcount = 1
+
+    def __init__(self, db):
+        self.db = db
+
+    def execute(self, cmd, args):
+        if self.db.raise_exc:
+            raise self.db.raise_exc
+        if self.db.last_cmd is None:
+            self.db.last_cmd = []
+        self.db.last_cmd.append((cmd, tuple(args)))
+
+    def fetchmany(self, count):
+        count /= 2      # Just to keep the code on it's toes.
+        try:
+            return self.db.result[:count]
+        finally:
+            del self.db.result[:count]
+
+    def close(self):
+        pass
+
+class DummyDB:
+    def close(self):
+        pass
+    def rollback(self):
+        pass
+    def commit(self):
+        pass
+
+class DummyDescriber(dbobj.DatabaseDescriberCore):
+    def __init__(self):
+        dbobj.DatabaseDescriberCore.__init__(self, testcommon.test_dsn)
+        self.reset()
+
+    def _connect_db(self):
+        self.db = DummyDB()
+
+    def reset(self, raise_exc=None, result=[]):
+        self.last_cmd = None
+        self.raise_exc = raise_exc
+        self.result = result
+
+    def cursor(self):
+        if not self.db:
+            self._connect_db()
+        return DummyCursor(self)
+
+db = DummyDescriber()
+td = db.new_table('testtable')
+td.column('id', dbobj.SerialColumn, primary_key=True)
+td.column('textcol', dbobj.StringColumn)
+td.column('intcol', dbobj.IntColumn)
+td.column('boolcol', dbobj.BooleanColumn, default='False')
+td.column('datecol', dbobj.DatetimeColumn, default='CURRENT_TIMESTAMP')
+td.column('lastupt', dbobj.LastUpdateColumn)
+td.column('missing', dbobj.IntColumn)
+
+table_name = '%s.%s' % (testcommon.test_dsn.database, 'testtable')
+
+
+class Case(unittest.TestCase):
+    def check_exec(self, expect):
+        def cmdstr(cmd):
+            if cmd is None:
+                return 'None'
+            cmd = ['%s args %s' % c for c in cmd]
+            cmd.insert(0, '')
+            return '\n    '.join(cmd)
+        self.assertEqual(db.last_cmd, expect,
+                         'Execute mismatch'
+                         '\nExpected: %s'
+                         '\nGot: %s' % 
+                            (cmdstr(expect), cmdstr(db.last_cmd)))
+
+    def test_null_result(self):
+        db.reset(result=[])
+        curs = db.cursor()
+        rs = dbobj.ResultSet(td)
+        rs.from_cursor(curs)
+        self.assertEqual(list(rs), [])
+        self.assertEqual(len(rs), 0)
+        self.assertEqual(rs.table_desc(), td)
+
+    def test_short_result(self):
+        db.reset(result=[
+            ('abc', -1, True, DateTime(2003,1,1), None, 0, 0),
+            ('def', -2, False, DateTime(2005,2,2), None, 2, 0),
+        ])
+        curs = db.cursor()
+        rs = dbobj.ResultSet(td)
+        rs.from_cursor(curs)
+        self.assertEqual(len(rs), 2)
+        self.assertEqual(rs.db_has_changed(), False)
+        rs.db_update()
+        self.check_exec(None)
+        self.assertEqual(rs[0].textcol, 'abc')
+        self.assertEqual(rs[1].textcol, 'def')
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=0, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+        self.assertEqual(rs[0].db_desc(), None)
+        self.assertEqual(rs[0].is_new(), False)
+        self.assertEqual(rs[0].db_has_changed(), False)
+        # Make sure we can pickle and restore the set
+        rs2 = loads(dumps(rs))
+        self.assertEqual(rs[0].textcol, 'abc')
+        self.assertEqual(rs[1].textcol, 'def')
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=0, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+
+        # Refetch
+        db.reset(result=[
+            ('abc', -1, True, DateTime(2003,1,1), None, 0, 0),
+        ])
+        rs[0].db_refetch()
+        self.check_exec([
+            ('SELECT * FROM testtable WHERE id=%s', (0,)),
+        ])
+
+    def test_long_result(self):
+        # We use fetchmany, so this tests our ability to repeatedly fetch
+        db.reset(result=[
+            ('abc', -1, True, DateTime(2003,1,1), None, 0, 0),
+            ('def', -2, False, DateTime(2005,2,2), None, 2, 0),
+            ('ghi', -4, False, DateTime(2002,3,4), None, 3, 0),
+        ] * 99)
+        curs = db.cursor()
+        rs = dbobj.ResultSet(td)
+        rs.from_cursor(curs)
+        self.assertEqual(len(rs), 99 * 3)
+
+    def get_rs(self):
+        db.reset(result=[
+            ('abc', -1, True, DateTime(2003,1,1), None, 0, 0),
+            ('def', -2, False, DateTime(2005,2,2), None, 2, 0),
+        ])
+        rs = dbobj.ResultSet(td)
+        rs.from_cursor(db.cursor())
+        return rs
+
+    def test_del(self):
+        rs = self.get_rs()
+        del rs[0]
+        self.assertEqual(len(rs), 1)
+        self.assertEqual(rs.db_has_changed(), True)
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=2, textcol='def', intcol=-2, boolcol=None, datecol=<cocklebur.datetime.mx_parse_datetime 02/02/2005 00:00:00>, lastupt=None, missing=None>" % table_name)
+        rs.db_revert()
+        self.assertEqual(rs.db_has_changed(), False)
+        self.assertEqual(len(rs), 2)
+        del rs[0]
+        rs.db_update()
+        self.check_exec([('DELETE FROM testtable WHERE id=%s', (0,))])
+
+        # Check that a revert after update is a no-op
+        rs.db_revert()
+        self.assertEqual(len(rs), 1)
+
+        # Check that deleting a row with dependancies raises an exception, and
+        # reverses the delete 
+        rs = self.get_rs()
+        db.reset(raise_exc=dbobj.IntegrityError)
+        del rs[0]
+        self.assertRaises(dbobj.IntegrityError, rs.db_update)
+        self.assertEqual(len(rs), 2)
+
+#       Hmmm - slice does not work
+#        rs = self.get_rs()
+#        del rs[:]
+#        self.assertEqual(len(rs), 0)
+#        rs.db_update()
+#        self.check_exec([
+#            ('DELETE FROM testtable WHERE id=%s', (0,)),
+#            ('DELETE FROM testtable WHERE id=%s', (2,)),
+#        ])
+
+    def test_pop(self):
+        rs = self.get_rs()
+        self.assertEqual(repr(rs.pop(0)), 
+            "<%s: id=0, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+        self.assertEqual(len(rs), 1)
+        rs.db_update()
+        self.check_exec([('DELETE FROM testtable WHERE id=%s', (0,))])
+
+    def test_remove(self):
+        rs = self.get_rs()
+        rs.remove(rs[1])
+        self.assertEqual(len(rs), 1)
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=0, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+        rs.db_update()
+        self.check_exec([('DELETE FROM testtable WHERE id=%s', (2,))])
+
+    def test_update(self):
+        rs = self.get_rs()
+        # Check changed row behaviour
+        rs[0].id = 3
+        self.assertEqual(rs.db_has_changed(), True)
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=3, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+        self.assertEqual(rs[0].db_desc(), 'testtable[id:0->3]')
+        # Check .db_revert()
+        rs.db_revert()
+        self.assertEqual(rs.db_has_changed(), False)
+        self.assertEqual(repr(rs[0]), 
+            "<%s: id=0, textcol='abc', intcol=-1, boolcol=True, datecol=<cocklebur.datetime.mx_parse_datetime 01/01/2003 00:00:00>, lastupt=None, missing=None>" % table_name)
+        # Check .db_update()
+        now1 = DateTime(2009, 8, 11, 10, 0, 0)
+        db.reset(result=[
+            ('lmn', -1, False, DateTime(2003,1,1), now1, 3, 0),
+        ])
+        rs[0].id = 3
+        rs[0].boolcol = ''
+        testcommon.freeze_time(now1, rs.db_update)
+        self.check_exec([
+            ('UPDATE testtable SET id=%s, boolcol=%s, lastupt=%s WHERE id=%s', 
+                (3, False, now1, 0)),
+            ('SELECT * FROM testtable WHERE id=%s', (3,)),
+        ])
+        self.assertEqual(rs.db_has_changed(), False)
+        self.assertEqual(rs[0].id, 3)
+        self.assertEqual(rs[0].boolcol, '')
+        self.assertEqual(rs[0].textcol, 'lmn')
+        self.assertEqual(rs[0].lastupt, now1)
+        # Check subsequent update
+        now2 = DateTime(2009, 8, 11, 10, 1, 0)
+        db.reset(result=[
+            ('lmn', -1, True, DateTime(2003,1,1), now2, 4, 0),
+        ])
+        rs[0].id = 4
+        testcommon.freeze_time(now2, rs.db_update)
+        self.check_exec([
+            ('UPDATE testtable SET id=%s, lastupt=%s WHERE id=%s', 
+                (4, now2, 3)),
+            ('SELECT * FROM testtable WHERE id=%s', (4,)),
+        ])
+        self.assertEqual(rs.db_has_changed(), False)
+        self.assertEqual(rs[0].id, 4)
+        self.assertEqual(rs[0].textcol, 'lmn')
+        self.assertEqual(rs[0].lastupt, now2)
+        # Check rollback() behaviour - for ResultRows, the effect is to
+        # (mostly) undo the effects of the last db_update(), so a subsequent
+        # db_update() will attempt to reapply any changes.
+        db.rollback()
+        self.assertEqual(rs.db_has_changed(), True)
+        self.assertEqual(rs[0].id, 4)
+        self.assertEqual(rs[0].lastupt, now1)
+
+    def test_insert(self):
+        rs = self.get_rs()
+        # Make a new row
+        row = rs.new_row()
+        row.id = 4
+        row.textcol = 'xyz'
+        row.intcol = 99
+        row.boolcol = 1
+        self.assertEqual(rs.db_has_changed(), False)
+        # Attach it to the rowset
+        rs.append(row)
+        self.assertEqual(row.is_new(), True)
+        self.assertEqual(len(rs), 3)
+        self.assertEqual(rs.db_has_changed(), True)
+        # Check a revert detaches it
+        rs.db_revert()
+        self.assertEqual(len(rs), 2)
+        self.assertEqual(rs.db_has_changed(), False)
+        # Check that it gets committed
+        rs.append(row)
+        db.reset(result=[
+            ('hij', 99, True, None, None, 4, 0),
+        ])
+        now = DateTime(2009, 8, 11, 10, 0, 0)
+        testcommon.freeze_time(now, rs.db_update)
+        self.assertEqual(row.is_new(), False)
+        self.check_exec([
+            ('INSERT INTO testtable (id,textcol,intcol,boolcol,lastupt) VALUES (%s,%s,%s,%s,%s)', (4, 'xyz', 99, dbobj.TRUE, now)),
+
+            ('SELECT * FROM testtable WHERE oid=%s', (999,)),
+        ])
+        # Now see if the refetch worked
+        self.assertEqual(row.id, 4)
+        self.assertEqual(row.textcol, 'hij')
+        self.assertEqual(row.intcol, 99)
+        self.assertEqual(row.boolcol, 'True') # This is to make web apps happy
+        self.assertEqual(row.datecol, None)
+        # A subsequent rollback should set us back to the pre-update state
+        db.rollback()
+        self.assertEqual(row.id, 4)
+        self.assertEqual(row.textcol, 'xyz')
+        # And we should now be able to do another update, and a commit, and any
+        # following rollbacks shouldn't touch us.
+        db.reset(result=[
+            ('hij', 99, False, None, None, 4, 0),
+        ])
+        rs.db_update()
+        db.commit()
+        db.rollback()
+        self.assertEqual(row.textcol, 'hij')
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'test_null_result',
+        'test_short_result',
+        'test_long_result',
+        'test_del',
+        'test_pop',
+        'test_remove',
+        'test_update',
+        'test_insert',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Case, self.test_list))
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/demogfields.py b/tests/demogfields.py
new file mode 100644
index 0000000..cd5109b
--- /dev/null
+++ b/tests/demogfields.py
@@ -0,0 +1,209 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os, sys
+import unittest
+
+from cocklebur import dbobj
+
+import testcommon
+
+from casemgr import demogfields
+
+#dbobj.execute_debug(1)
+
+thisdir = os.path.dirname(__file__)
+scratchdir = os.path.join(thisdir, 'scratch')
+
+CD_FIELD = 2    # Pick an arbitrary field to test - case definition
+
+class DemogFieldsTest(testcommon.DBTestCase):
+
+    def setUp(self):
+        td = self.new_table(demogfields.DEMOG_TABLE)
+        # This needs to be kept in sync the schema, unfortunately - we can't
+        # use the schema directly, or we'd have to create all the dependancies
+        # as well.
+        td.column('synddf_id', dbobj.SerialColumn, primary_key = True)
+        td.column('syndrome_id', dbobj.IntColumn)
+        td.column('name', dbobj.StringColumn)
+        td.column('label', dbobj.StringColumn)
+        td.column('show_case', dbobj.BooleanColumn, default = 'True')
+        td.column('show_form', dbobj.BooleanColumn, default = 'True')
+        td.column('show_search', dbobj.BooleanColumn, default = 'True')
+        td.column('show_person', dbobj.BooleanColumn, default = 'True')
+        td.add_index('sdf_syndrome_id', ['syndrome_id'])
+        td.add_index('sdf_si_name_idx', ['syndrome_id', 'name'], unique=True)
+        td.create()
+        self.db.commit()
+
+    def execute(self, cmd, args):
+        curs = self.db.cursor()
+        try:
+            dbobj.execute(curs, cmd % demogfields.DEMOG_TABLE, args)
+        finally:
+            curs.close()
+
+    def user(self):
+        # Check defaults derived from classes with no local override
+        common_fields = demogfields.get_demog_fields(self.db, None)
+        self.assertEqual(common_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(common_fields[CD_FIELD].show('case'), True)
+        self.assertEqual(common_fields[CD_FIELD].show('search'), True)
+        self.assertEqual(common_fields[CD_FIELD].show('person'), False)
+
+        # Syndrome-specific fields with no override
+        synd_fields = demogfields.get_demog_fields(self.db, None)
+        self.assertEqual(synd_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(synd_fields[CD_FIELD].show('case'), True)
+        self.assertEqual(synd_fields[CD_FIELD].show('search'), True)
+        self.assertEqual(synd_fields[CD_FIELD].show('person'), False)
+
+        # Now check defaults with db-derived common override
+        self.execute('INSERT INTO %s (name, label, show_case)'
+                     ' VALUES (%%s, %%s, %%s)',
+                     ('case_definition', 'LABEL', False))
+        demogfields.flush()
+        common_fields = demogfields.get_demog_fields(self.db, None)
+        self.assertEqual(common_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(common_fields[CD_FIELD].label, 'LABEL')
+        self.assertEqual(common_fields[CD_FIELD].show('case'), False)
+        self.assertEqual(common_fields[CD_FIELD].show('search'), True)
+        self.assertEqual(common_fields[CD_FIELD].show('person'), False)
+
+        # Syndrome-specific fields with common override
+        demogfields.flush()
+        synd_fields = demogfields.get_demog_fields(self.db, 1)
+        self.assertEqual(synd_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(synd_fields[CD_FIELD].label, 'LABEL')
+        self.assertEqual(synd_fields[CD_FIELD].show('case'), False)
+        self.assertEqual(synd_fields[CD_FIELD].show('search'), True)
+        self.assertEqual(synd_fields[CD_FIELD].show('person'), False)
+
+        # Now check Syndrome-specific with Syndrome-specific override
+        self.execute('INSERT INTO %s (syndrome_id, name, label, show_search)'
+                     ' VALUES (%%s, %%s, %%s, %%s)',
+                     (1, 'case_definition', 'FROG', False))
+        demogfields.flush()
+        synd_fields = demogfields.get_demog_fields(self.db, 1)
+        self.assertEqual(synd_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(synd_fields[CD_FIELD].label, 'FROG')
+        self.assertEqual(synd_fields[CD_FIELD].show('case'), True)
+        self.assertEqual(synd_fields[CD_FIELD].show('search'), False)
+        self.assertEqual(synd_fields[CD_FIELD].show('person'), False)
+
+        # And verify common values have not changed
+        demogfields.flush()
+        common_fields = demogfields.get_demog_fields(self.db, None)
+        self.assertEqual(common_fields[CD_FIELD].name, 'case_definition')
+        self.assertEqual(common_fields[CD_FIELD].label, 'LABEL')
+        self.assertEqual(common_fields[CD_FIELD].show('case'), False)
+        self.assertEqual(common_fields[CD_FIELD].show('search'), True)
+        self.assertEqual(common_fields[CD_FIELD].show('person'), False)
+
+    def admin(self):
+        def _check(label, show_case, show_form, show_search, show_person):
+            def _assert(field, want):
+                got = getattr(fields[CD_FIELD], field)
+                self.assertEqual(got, want, 'field %r: got %r wanted %r' % 
+                                        (field, got, want))
+            _assert('name', 'case_definition')
+            if label:
+                _assert('label', label)
+            _assert('show_case', show_case)
+            _assert('show_form', show_form)
+            _assert('show_search', show_search)
+            _assert('show_person', show_person)
+
+        #dbobj.execute_debug(1)
+
+        # Check initial state, per syndrome
+        fields = demogfields.DemogFields(self.db, 1)
+        _check(None, True, True, True, False)
+
+        # Now edit, commit, and reload
+        fields[CD_FIELD].label = 'SALAMANDA'
+        fields[CD_FIELD].show_form = False
+        fields.update(self.db)
+        fields = demogfields.DemogFields(self.db, 1)
+        _check('SALAMANDA', True, False, True, False)
+
+        # Common/default initial state
+        fields = demogfields.DemogFields(self.db, None)
+        _check(None, True, True, True, False)
+
+        # Now edit, commit, and reload
+        fields[CD_FIELD].label = 'CATFISH'
+        fields[CD_FIELD].show_search = False
+        fields.update(self.db)
+        fields = demogfields.DemogFields(self.db, None)
+        _check('CATFISH', True, True, False, False)
+
+    def field_api(self):
+        def _field_api(field):
+            self.failUnless(hasattr(field, 'disabled'))
+            self.failUnless(hasattr(field, 'entity'))
+            self.failUnless(hasattr(field, 'field'))
+            self.failUnless(hasattr(field, 'hideable'))
+            self.failUnless(hasattr(field, 'label'))
+            self.failUnless(hasattr(field, 'name'))
+            self.failUnless(hasattr(field, 'render'))
+            self.failUnless(hasattr(field, 'section'))
+            for context in demogfields.contexts:
+                self.failUnless(hasattr(field, 'show_' + context))
+                self.failUnless(field.show(context) in (True, False))
+        for field in demogfields.get_demog_fields(self.db, None):
+            _field_api(field)
+        for field in demogfields.get_demog_fields(self.db, 0):
+            _field_api(field)
+
+    def context_field_api(self):
+        def _field_api(field):
+            self.failUnless(hasattr(field, 'context'))
+            self.failUnless(hasattr(field, 'disabled'))
+            self.failUnless(hasattr(field, 'entity'))
+            self.failUnless(hasattr(field, 'field'))
+            self.failUnless(hasattr(field, 'hideable'))
+            self.failUnless(hasattr(field, 'label'))
+            self.failUnless(hasattr(field, 'name'))
+            self.failUnless(hasattr(field, 'render'))
+            self.failUnless(hasattr(field, 'section'))
+        fields = demogfields.get_demog_fields(self.db, None)
+        for context in demogfields.contexts:
+            for field in fields.context_fields(context):
+                _field_api(field)
+                
+
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'user',
+        'admin',
+        'field_api',
+        'context_field_api',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(DemogFieldsTest, self.test_list))
+
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tests/export.py b/tests/export.py
new file mode 100644
index 0000000..67301eb
--- /dev/null
+++ b/tests/export.py
@@ -0,0 +1,117 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+"""
+Quick and somewhat dirty regression tests for data export code.
+
+This could form the basis for more general unittests of the casemgr
+machinery, because it populates a skeleton casemgr test database.
+
+$Id: export.py 4432 2011-03-03 01:08:38Z andrewm $
+$Source$
+"""
+import os
+import unittest
+import time
+import csv
+import cStringIO
+import itertools
+
+thisdir = os.path.dirname(__file__)
+exportdatadir = os.path.join(thisdir, 'data', 'export')
+
+from cocklebur import dbobj
+#dbobj.execute_debug(1)
+
+import testcommon
+
+import casemgr
+from casemgr import globals
+from casemgr.dataexport import CaseExporter
+
+
+def cellname(row, col):
+    "Generate spreadsheet-style row and col addressing"
+    label = ''
+    while not label or col:
+        col, mod = divmod(col, 26)
+        label = chr(65 + mod) + label
+    return label + str(row+1)
+
+def csv_cmp(a, b):
+    fa = csv.reader(open(a, 'rb'))
+    fb = csv.reader(open(b, 'rb'))
+    res = []
+    for row, (rowa, rowb) in enumerate(map(None, fa, fb)):
+        for col, (cola, colb) in enumerate(map(None, rowa, rowb)):
+            if cola != colb:
+                res.append('%s %s != %s' % (cellname(row, col), cola, colb))
+    return '\n'.join(res)
+
+
+class ExportTest(testcommon.AppTestCase):
+
+    def _test(self, result_file, **kwargs):
+        credentials = testcommon.DummyCredentials()
+        syndrome = testcommon.DummySyndrome()
+        exporter = CaseExporter(credentials, 2, **kwargs)
+        self.assertEqual([form.label for form in exporter.forms], 
+                         ['sars_exposure', 'hospital_admit'])
+        self.assertEqual(len(exporter.export_cases), 3)
+        ids = [ec.id for ec in exporter.export_cases.cases_in_order]
+        self.assertEqual(ids, [1, 2, 5])
+        cwd = os.getcwd()
+        try:
+            f = open('result.csv', 'wb')
+            os.chdir(os.path.join(os.path.dirname(__file__), '..', 'casemgr'))
+            exporter.csv_write(['sars_exposure'], f)
+            os.chdir(cwd)
+            f.flush()
+            res = csv_cmp('result.csv', 
+                          os.path.join(exportdatadir, result_file))
+            self.failIf(res, '\n'+res)
+        finally:
+            os.chdir(cwd)
+            os.unlink('result.csv')
+
+    def test_classic(self):
+        self._test('result.csv', format='classic')
+
+    def test_doha(self):
+        self._test('doharesult.csv', format='doha')
+
+    def test_form(self):
+        self._test('formresult.csv', format='form')
+
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'test_classic',
+        'test_doha',
+        'test_form',
+    )
+
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(ExportTest, self.test_list))
+
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/tests/form.py b/tests/form.py
new file mode 100644
index 0000000..8442653
--- /dev/null
+++ b/tests/form.py
@@ -0,0 +1,83 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# This module tests form object instantiation.
+
+import unittest
+from cocklebur import form_ui
+
+class FormTest(unittest.TestCase):
+    def minimal_form(self):
+        form = form_ui.Form('A test form')
+        self.assertEqual(form.text, 'A test form')
+        self.assertEqual(form.name, None)
+        self.assertEqual(form.version, None)
+        self.assertEqual(form.form_type, 'case')
+        self.assertEqual(form.allow_multiple, False)
+        self.assertEqual(form.table, None)
+        args = dict(name='testform_name', form_type='testform_type',
+                    allow_multiple=True)
+        form = form_ui.Form('A test form', **args)
+        self.assertEqual(form.text, 'A test form')
+        for k, v in args.items():
+            av = getattr(form, k)
+            self.assertEqual(av, v, '%s: attr %s != arg %s' % (k, av, v))
+
+    def form_with_section_and_questions(self):
+        form = form_ui.Form('A test form')
+        section = form_ui.Section('A section')
+        form.append(section)
+        self.assertEqual(form.text, 'A test form')
+        self.assertEqual(len(form), 1)
+        self.assertEqual(len(form.children[0]), 0)
+        self.assertEqual(form.children[0].text, 'A section')
+        section.question('A question')
+        self.assertEqual(len(form.children[0]), 1)
+        question = form.children[0].children[0]
+        self.assertEqual(question.text, 'A question')
+        self.assertEqual(question.inputs, [])
+        self.assertEqual(question.help, None)
+        self.assertEqual(question.disabled, False)
+        section.question('A second question', help='Some help', disabled=True)
+        self.assertEqual(len(form.children[0]), 2)
+        question = form.children[0].children[1]
+        self.assertEqual(question.text, 'A second question')
+        self.assertEqual(question.inputs, [])
+        self.assertEqual(question.help, 'Some help')
+        self.assertEqual(question.disabled, True)
+        form.question('A form question')
+        self.assertEqual(len(form), 2)
+        question = form.children[1]
+        self.assertEqual(question.text, 'A form question')
+
+
+class FormSuite(unittest.TestSuite):
+    test_list = (
+        'minimal_form',
+        'form_with_section_and_questions',
+    )
+
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(FormTest, self.test_list))
+
+def suite():
+    return FormSuite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tests/formcommon.py b/tests/formcommon.py
new file mode 100644
index 0000000..ba7b725
--- /dev/null
+++ b/tests/formcommon.py
@@ -0,0 +1,40 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from cocklebur import form_ui
+
+def get_testform():
+    form = form_ui.Form(name='testform', text='Test Form', form_type='testform')
+    section = form_ui.Section('Section 1')
+    section.question(text='First Question',
+        input = form_ui.YesNo('input_a', required=True,
+                              summary='Input A'))
+    section.question(text='Second Question',
+        inputs = [
+            form_ui.TextInput('q2a', pre_text='Part A'),
+            form_ui.TextInput('q2b', pre_text='Part B'),
+            form_ui.TextInput('q2c', pre_text='Part C'),
+        ])
+    form.append(section)
+    form.question(text='Third Question',
+        input = form_ui.RadioList('q3', required=True, direction='horizontal',
+            choices=[
+                ('a', 'A'),
+                ('b', 'B'),
+                ('c', 'C'),
+            ]))
+    return form
diff --git a/tests/formlib.py b/tests/formlib.py
new file mode 100644
index 0000000..61dd13a
--- /dev/null
+++ b/tests/formlib.py
@@ -0,0 +1,153 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os, sys
+import shutil
+import unittest
+import copy
+from cStringIO import StringIO
+
+from cocklebur import dbobj
+from cocklebur import form_ui
+
+import testcommon
+import formcommon
+
+#dbobj.execute_debug(1)
+
+thisdir = os.path.dirname(__file__)
+scratchdir = os.path.join(thisdir, 'scratch')
+
+class _FormLibTests(unittest.TestCase):
+
+    def runTest(self):
+        # No forms to start with
+        self.assertEqual(list(self.formlib), [])
+        self.assertEqual(len(self.formlib), 0)
+        self.assertRaises(form_ui.NoFormError, 
+                          self.formlib.load, 'testform', 1)
+        self.assertEqual(self.formlib.versions('testform'), [])
+
+        # Create a form, save and restore
+        testform = formcommon.get_testform()
+        saved_version = self.formlib.save(testform, 'testform')
+        self.assertEqual(saved_version, 1)
+        self.assertEqual(testform.version, 1)
+        self.assertEqual(testform.name, 'testform')
+        self.assertEqual(testform.table, 'form_testform_00001')
+        self.assertEqual(len(self.formlib), 1)
+        available = list(self.formlib)
+        self.assertEqual(len(available), 1)
+        self.assertEqual(available[0].name, 'testform')
+        self.assertEqual(available[0].version, 1)
+        self.assertEqual(self.formlib.versions('testform'), [1])
+        loadedform = available[0].load()
+        self.assertEqual(testform, loadedform)
+        self.assertEqual(loadedform.version, 1)
+        self.assertEqual(loadedform.name, 'testform')
+        self.assertEqual(loadedform.table, 'form_testform_00001')
+
+        # Modify the form, save and restore
+        newform = copy.deepcopy(testform)
+        newform.text = 'Modified test form'
+        newform.children[1].text = 'Modified Third Question'
+        del newform.children[1].inputs[0].choices[2]
+        saved_version = self.formlib.save(newform, 'testform')
+        self.assertEqual(saved_version, 2)
+        self.assertEqual(len(self.formlib), 2)
+        available = list(self.formlib)
+        self.assertEqual(len(available), 2)
+        self.assertEqual(available[0].name, 'testform')
+        self.assertEqual(available[0].version, 1)
+        self.assertEqual(available[1].version, 2)
+        self.assertEqual(self.formlib.versions('testform'), [1, 2])
+        loadedform = available[0].load()
+        self.assertEqual(testform, loadedform)
+        loadedform = available[1].load()
+        self.assertEqual(newform, loadedform)
+        self.assertEqual(loadedform.version, 2)
+        self.assertEqual(loadedform.name, 'testform')
+        self.assertEqual(loadedform.table, 'form_testform_00002')
+
+        # Save As
+        saved_version = self.formlib.save(newform, 'altform')
+        self.assertEqual(saved_version, 1)
+        self.assertEqual(len(self.formlib), 3)
+        available = list(self.formlib)
+        self.assertEqual(len(available), 3)
+        self.assertEqual(available[0].name, 'altform')
+        self.assertEqual(available[0].version, 1) 
+        self.assertEqual(available[1].name, 'testform')
+        self.assertEqual(available[2].name, 'testform')
+        self.assertEqual(newform.version, 1)
+        self.assertEqual(newform.name, 'altform')
+        self.assertEqual(newform.table, 'form_altform_00001')
+
+        # Rename the form
+        self.assertRaises(form_ui.DuplicateFormError, self.formlib.rename, 
+                          'testform', 'altform')
+        self.assertRaises(form_ui.NoFormError, self.formlib.rename, 
+                          'noform', 'newtestform')
+        self.formlib.rename('testform', 'newtestform')
+        available = list(self.formlib)
+        self.assertEqual(len(available), 3)
+        self.assertEqual(available[0].name, 'altform')
+        self.assertEqual(available[1].name, 'newtestform')
+        self.assertEqual(available[1].version, 1)
+        self.assertEqual(available[2].name, 'newtestform')
+        self.assertEqual(available[2].version, 2)
+
+        # Delete the form
+        self.assertRaises(form_ui.NoFormError, self.formlib.delete, 'noform')
+        self.formlib.delete('newtestform')
+        available = list(self.formlib)
+        self.assertEqual(len(available), 1)
+        self.assertEqual(available[0].name, 'altform')
+
+
+class FormLibPyFilesCase(_FormLibTests):
+
+    def setUp(self):
+        os.mkdir(scratchdir)
+        self.formlib = form_ui.FormLibPyFiles(scratchdir)
+
+    def tearDown(self):
+        shutil.rmtree(scratchdir)
+
+
+class FormLibXMLDBCase(_FormLibTests, testcommon.DBTestCase):
+
+    def setUp(self):
+        td = self.new_table('form_defs')
+        td.column('name', dbobj.StringColumn)
+        td.column('version', dbobj.IntColumn)
+        td.column('xmldef', dbobj.StringColumn)
+        td.add_index('fd_namever_idx', ['name', 'version'], unique=True)
+        td.add_index('fd_name_idx', ['name'])
+        td.create()
+        self.formlib = form_ui.FormLibXMLDB(self.db, 'form_defs')
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(FormLibPyFilesCase())
+    suite.addTest(FormLibXMLDBCase())
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tests/formsave.py b/tests/formsave.py
new file mode 100644
index 0000000..d59ad32
--- /dev/null
+++ b/tests/formsave.py
@@ -0,0 +1,125 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os
+import shutil
+from cStringIO import StringIO
+import unittest
+
+from cocklebur import form_ui
+from cocklebur.form_ui.xmlsave import xmlsave
+from cocklebur.form_ui.xmlload import xmlload
+from cocklebur.form_ui.pysave import pysave
+from cocklebur.form_ui.pyload import pyload
+
+scratchdir = os.path.join(os.path.dirname(__file__), 'scratch')
+scratchfile = os.path.join(scratchdir, 'testform.py')
+
+class _FormSaveTests(unittest.TestCase):
+    def _test(self, form):
+        try:
+            os.mkdir(scratchdir)
+            f = open(scratchfile, 'w')
+            try:
+                self.save(f, form)
+            finally:
+                f.close()
+#            print open(scratchfile, 'r').read()
+            f = open(scratchfile, 'r')
+            try:
+                loaded = self.load(f)
+            finally:
+                f.close()
+            self.assertEqual(form, loaded)
+        finally:
+            shutil.rmtree(scratchdir)
+
+    def runTest(self):
+        # Some string that will cause a parse error from all our loaders:
+        self.assertRaises(form_ui.FormParseError, self.load, StringIO('<abc'))
+
+        form = form_ui.Form('Test form', name='testform',
+                            form_type='abc', allow_multiple=True)
+        self._test(form)
+
+        section = form_ui.Section('Section A')
+        form.append(section)
+        self._test(form)
+
+        form.question(text='Question A', 
+            input=form_ui.TextInput('input_a', summary='xyz blah'))
+        section.question(text='Question B', 
+            disabled=True, help='AbC<p>dEf&gh',
+            trigger_mode='enable',
+            triggers=['input_c'],
+            input=form_ui.TextInput('input_b', 
+                pre_text='pretextxx', post_text='posttextyy', maxsize=10))
+        self._test(form)
+
+        section.question(text='Question C', 
+            input=form_ui.CheckBoxes('input_c',
+                choices = [
+                    ('a', 'Choice A'),
+                    ('b', 'Choice B')],
+                skips = [
+                    form_ui.Skip('input_c', ['a'], True),
+                    form_ui.Skip('input_c', ['b'], 
+                        not_selected=True, show_msg=False, 
+                        skip_remaining=False)],
+                default='b', required=True,
+                direction='horizontal'))
+
+        section.question(text='Question D', 
+            inputs = [
+                form_ui.FloatInput('input_da',
+                    minimum=10, maximum=20),
+                form_ui.IntInput('input_db',
+                    minimum=10, maximum=20)])
+
+        self._test(form)
+
+class XMLTests(_FormSaveTests):
+    def save(self, f, form):
+        xmlsave(f, form)
+
+    def load(self, f):
+        return xmlload(f)
+
+    # For backtraces
+    def runTest(self):
+        _FormSaveTests.runTest(self)
+
+class PYTests(_FormSaveTests):
+    def save(self, f, form):
+        pysave(f, form)
+
+    def load(self, f):
+        return pyload(f)
+
+    # For backtraces
+    def runTest(self):
+        _FormSaveTests.runTest(self)
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(XMLTests())
+    suite.addTest(PYTests())
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/tests/parse_addr.py b/tests/parse_addr.py
new file mode 100644
index 0000000..cca0f0b
--- /dev/null
+++ b/tests/parse_addr.py
@@ -0,0 +1,71 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import unittest
+
+from cocklebur.utils import parse_addr
+
+class AddressTest(unittest.TestCase):
+
+    def _test(self, addr, user, domain):
+        self.assertEqual(parse_addr(addr), (user, domain))
+
+    def bad_address(self):
+        self.assertRaises(ValueError, parse_addr, '')
+        self.assertRaises(ValueError, parse_addr, '@')
+        self.assertRaises(ValueError, parse_addr, 'foo@')
+        self.assertRaises(ValueError, parse_addr, '@example.com')
+        self.assertRaises(ValueError, parse_addr, 'foo at example')
+        self.assertRaises(ValueError, parse_addr, '<foo at example>')
+        self.assertRaises(ValueError, parse_addr, 'foo at bah@example.com')
+
+    def bare_address(self):
+        self._test('foo at example.com', 'foo', 'example.com')
+        self._test('foo at example.com.au', 'foo', 'example.com.au')
+        self._test(' foo at example.com ', 'foo', 'example.com')
+        self._test(' foo at example.com. ', 'foo', 'example.com')
+        self._test('(biz at flibble.com) foo at example.com', 'foo', 'example.com')
+        self._test('(biz at flibble.com) foo at example.com.', 'foo', 'example.com')
+        self._test('foo at example.com (biz at flibble.com)', 'foo', 'example.com')
+        self._test('foo at example.com. (biz at flibble.com)', 'foo', 'example.com')
+        self._test('foo at EXAMPLE.com (biz at flibble.com)', 'foo', 'example.com')
+
+    def delim_address(self):
+        self._test('<foo at example.com>', 'foo', 'example.com')
+        self._test('<foo at example.com.>', 'foo', 'example.com')
+        self._test('<foo at example.com.au>', 'foo', 'example.com.au')
+        self._test(' <foo at example.com> ', 'foo', 'example.com')
+        self._test('biz at flibble.com <foo at example.com>', 'foo', 'example.com')
+        self._test('<foo at example.com> biz at flibble.com', 'foo', 'example.com')
+        self._test('"biz at flibble.com" <foo at example.com>', 'foo', 'example.com')
+        self._test('<foo at example.com> "biz at flibble.com"', 'foo', 'example.com')
+        self._test('<foo at EXAMPLE.com> "biz at flibble.com"', 'foo', 'example.com')
+
+
+class suite(unittest.TestSuite):
+    test_list = (
+        'bad_address',
+        'bare_address',
+        'delim_address',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(AddressTest, self.test_list))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/tests/person.py b/tests/person.py
new file mode 100644
index 0000000..5f9ca19
--- /dev/null
+++ b/tests/person.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import unittest
+
+from casemgr import person
+
+import testcommon
+
+class SoftTitleCase(unittest.TestCase):
+
+    def test(self):
+        def test(inp, expect):
+            self.assertEqual(person.soft_titlecase(inp), expect)
+        test('fred flintstone', 'Fred Flintstone')
+        test('FRED FLINTSTONE', 'Fred Flintstone')
+        test('fred flintstonE', 'Fred flintstonE')
+        test('Fred flintstonE', 'Fred flintstonE')
+        test('Barney von Rubble', 'Barney von Rubble')
+        test('barney von Rubble', 'barney von Rubble')
+        test('barney von rubble', 'Barney von Rubble')
+        test('michael o\'rourke', 'Michael O\'Rourke')
+        test('Michael O\'Rourke', 'Michael O\'Rourke')
+        test('rip van winkle', 'Rip Van Winkle')        # hmm
+        test('jane smith-jones', 'Jane Smith-Jones')
+        test('McNamara', 'McNamara')
+        test('frog\'s hollow', 'Frog\'s Hollow')
+        test('No address supplied', 'No address supplied')
+        test('nORTHSTYNE rOAD', 'nORTHSTYNE rOAD')      # Could do better
+        test('"neverseen" hidden valley', '"Neverseen" Hidden Valley')
+        test("'neverseen' hidden valley", "'neverseen' Hidden Valley") #Hmm
+        test("'Neverseen' Hidden Valley", "'Neverseen' Hidden Valley")
diff --git a/tests/persondupe.py b/tests/persondupe.py
new file mode 100644
index 0000000..d293fc6
--- /dev/null
+++ b/tests/persondupe.py
@@ -0,0 +1,140 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+from mx.DateTime import DateTime
+
+import testcommon
+
+from casemgr import persondupe, persondupecfg
+
+
+class NGramWrapper(persondupe.NGram):
+    ngram_map = {}
+
+    def __init__(self, *values):
+        self.fields = []
+        for i, v in enumerate(values):
+            fn = 'field_%d' % i
+            setattr(self, fn, v)
+            self.fields.append(fn)
+        persondupe.NGram.__init__(self, None, self)
+
+
+class NGramTest(unittest.TestCase):
+    def runTest(self):
+        # Name matching
+        a = NGramWrapper('Smith')
+        b = NGramWrapper('Smithe')
+        c = NGramWrapper('Smith', 'Smith')
+        d = NGramWrapper('Jane', 'Smith')
+        e = NGramWrapper('Jane', 'Clark')
+        for ng in (a, b, c, d, e):
+            ng.prescan()
+        # Invalid to compare to self in current implementation
+        # self.assertAlmostEqual(a.match(a), 1.00, 2)
+        self.assertAlmostEqual(a.match(b), 0.73, 2)
+        self.assertAlmostEqual(b.match(a), 0.73, 2)
+        self.assertAlmostEqual(a.match(c), 1.00, 2)
+        self.assertAlmostEqual(a.match(d), 0.71, 2)
+        self.assertAlmostEqual(d.match(a), 0.71, 2)
+        self.assertAlmostEqual(a.match(e), 0.00, 2)
+        self.assertAlmostEqual(e.match(a), 0.00, 2)
+        # Phone number matching?
+        a = NGramWrapper('1234-5678')
+        b = NGramWrapper('1234-5678', '1234-1234')
+        c = NGramWrapper('1234-5678', '4321-0982')
+        d = NGramWrapper('12312123', '4321-0982')
+        for ng in (a, b, c, d):
+            ng.prescan()
+        self.assertAlmostEqual(a.match(b), 0.86, 2)
+        self.assertAlmostEqual(a.match(c), 0.67, 2)
+        self.assertAlmostEqual(a.match(d), 0.00, 2)
+
+class Person:
+    id = 0
+    def __init__(self, surname, given_names, sex=None, DOB=None, DOB_prec=0,
+                 street_address=None, locality=None, 
+                 state=None, postcode=None, country=None,
+                 alt_street_address=None, alt_locality=None, 
+                 alt_state=None, alt_postcode=None, alt_country=None,
+                 work_street_address=None, work_locality=None, 
+                 work_state=None, work_postcode=None, work_country=None,
+                 passport_number=None, passport_country=None,
+                 passport_number_2=None, passport_country_2=None,
+                 home_phone=None, work_phone=None,
+                 mobile_phone=None, fax_phone=None,
+                 e_mail=None, last_update=None):
+        self.__dict__.update(vars())
+        if self.DOB:
+            self.DOB = DateTime(*[int(d) for d in self.DOB.split('-')])
+        Person.id += 1
+        self.person_id = self.id
+        self.last_update = None
+
+
+class MatchPersons(persondupe.MatchPersons):
+    """
+    We subclass so we can override methods that touch the db
+    """
+    test_records = [
+        Person('Smith', 'John'),
+        Person('Smithe', 'John'),
+        Person('Jackson', 'John'),
+        Person('John', 'Jackson'),
+        Person('Jane', 'Doe'),
+        Person('Doe', 'Jane', street_address='4/34 Smith St', DOB='1960-5-5'),
+        Person('Jones', 'Jane', street_address='4/34 Smith St', DOB='1960-5-5'),
+    ]
+    
+    def lock(self, db):
+        pass
+
+    def load(self, db, update_only):
+        assert not update_only
+        matchers = persondupe.get_matchers(persondupecfg.new_persondupecfg())
+        for row in self.test_records:
+            self.records.append(persondupe.Record(row, matchers))
+        self.last_run = None
+
+
+class PersonDupeTest(unittest.TestCase):
+    def runTest(self):
+        real_dupe_lock = persondupe.dupe_lock
+        persondupe.dupe_lock = lambda a, b: None
+        try:
+            mp = MatchPersons(None)
+        finally:
+            persondupe.dupe_lock = real_dupe_lock
+        likely = [(pair.low_person_id, pair.high_person_id) 
+                   for pair in mp.dupes.sorted()]
+        self.assertEqual(likely, [
+            (6, 7),
+            (3, 4),
+            (5, 6),
+            (1, 2),
+        ])
+        
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(NGramTest())
+    suite.addTest(PersonDupeTest())
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
diff --git a/tests/reports/__init__.py b/tests/reports/__init__.py
new file mode 100644
index 0000000..a08629f
--- /dev/null
+++ b/tests/reports/__init__.py
@@ -0,0 +1,18 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+#
diff --git a/tests/reports/columns.py b/tests/reports/columns.py
new file mode 100644
index 0000000..9f94d4d
--- /dev/null
+++ b/tests/reports/columns.py
@@ -0,0 +1,134 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import reports
+
+from tests.reports import common
+from tests import testcommon
+
+expect_availcols = [
+    '',
+    'home_phone,work_phone,mobile_phone',
+    'age', 'DOB_only', 'deleted',
+    'delete_timestamp', 'delete_reason', 'fax_phone', 'home_phone',
+    'interpreter_req', 'mobile_phone',
+    'notification_datetime', 'passport_country', 'passport_number',
+    'postcode', 'street_address', 'work_phone',
+]
+
+expect_form_fields = [
+    'close_contact', 'contact_duration', 'contact_date_first', 
+    'contact_date_last', 'contact_favourite_food'
+]
+
+available_form_fields =  [
+    '', '!all', '!summary',
+    'contact_duration', 'close_contact', 'contact_date_first',
+    'contact_favourite_food', 'contact_date_last',
+]
+
+class ReportOutgroupTests(testcommon.AppTestCase):
+
+    def test_outgroup(self):
+        """
+        output column parameter handling
+        """
+        params = reports.new_report(2, 'line')
+        # List of available demog fields
+        self.assertEqual(len(params.outgroups), 2)
+        availcols = [c[0] for c in params.outgroups[0].available_cols()]
+        self.assertEqual(availcols, expect_availcols)
+        self.assertEqual(len(params.outgroups[0]), 12)
+        self.assertEqual(params.outgroups[0].labels(), common.expect_headings)
+
+        # Group movement
+        self.assertEqual(params.has_up(0), False)
+        self.assertEqual(params.has_dn(0), False)
+        self.assertEqual(params.has_up(1), False)
+        self.assertEqual(params.has_dn(1), False)
+        params.add_caseperson()
+        self.assertEqual(len(params.outgroups), 3)
+        self.assertEqual(params.has_dn(1), True)
+        self.assertEqual(params.has_dn(2), False)
+        self.assertEqual(params.has_up(2), True)
+        params.colop('gdel', 2)
+        # demog fields "float" to the top
+        params.add_form('sars_exposure')
+        params.add_caseperson()
+        self.assertEqual(params.outgroups[2].form_name, None)
+        self.assertEqual(params.outgroups[3].form_name, 'sars_exposure')
+        params.add_form('hospital_admit')
+        params.colop('gdn', 3)
+        self.assertEqual(params.outgroups[3].form_name, 'hospital_admit')
+        self.assertEqual(params.outgroups[4].form_name, 'sars_exposure')
+        # no-op at end of list
+        params.colop('gdn', 4)
+        self.assertEqual(params.outgroups[3].form_name, 'hospital_admit')
+        self.assertEqual(params.outgroups[4].form_name, 'sars_exposure')
+        params.colop('gup', 4)
+        self.assertEqual(params.outgroups[3].form_name, 'sars_exposure')
+        self.assertEqual(params.outgroups[4].form_name, 'hospital_admit')
+        # Can't move form above demog
+        params.colop('gup', 3)
+        self.assertEqual(params.outgroups[3].form_name, 'sars_exposure')
+        self.assertEqual(params.outgroups[4].form_name, 'hospital_admit')
+
+        # List of available form fields
+        availcols = [c[0] for c in params.outgroups[3].available_cols()]
+        self.assertEqual(availcols, available_form_fields)
+        # Add "all" form fields
+        self.assertEqual(params.outgroups[3].names(), [])
+        params.outgroups[3].addcol = '!all'
+        params.cols_update()
+        self.assertEqual(params.outgroups[3].names(), expect_form_fields)
+        # Add "summary" form fields
+        params.colop('gdel', 3)
+        params.add_form('sars_exposure')
+        params.outgroups[4].addcol = '!summary'
+        params.cols_update()
+        self.assertEqual(params.outgroups[4].names(), expect_form_fields)
+        # Add specific form fields
+        params.colop('gdel', 4)
+        params.add_form('sars_exposure')
+        params.outgroups[4].addcol = 'contact_date_last,contact_favourite_food'
+        params.cols_update()
+        params.outgroups[4].addcol = 'close_contact'
+        params.cols_update()
+        self.assertEqual(params.outgroups[4].names(), 
+            ['contact_date_last', 'contact_favourite_food', 'close_contact'])
+
+        # Field movement
+        # Move up or down should have no effect at ends of list
+        params.colop('up', 0, 0)
+        params.colop('dn', 0, len(params.outgroups[0]) - 1)
+        self.assertEqual(params.outgroups[0].labels(), common.expect_headings)
+        # Test up/dn/del
+        expect = list(common.expect_headings)
+        expect[0], expect[1] = expect[1], expect[0]
+        params.colop('up', 0, 1)
+        self.assertEqual(params.outgroups[0].labels(), expect)
+        expect[2], expect[3] = expect[3], expect[2]
+        params.colop('dn', 0, 2)
+        self.assertEqual(params.outgroups[0].labels(), expect)
+        del expect[4]
+        params.colop('del', 0, 4)
+        self.assertEqual(params.outgroups[0].labels(), expect)
+        # Test clear
+        params.colop('clear', 0)
+        self.assertEqual(len(params.outgroups[0]), 0)
+
diff --git a/tests/reports/common.py b/tests/reports/common.py
new file mode 100644
index 0000000..e4aba85
--- /dev/null
+++ b/tests/reports/common.py
@@ -0,0 +1,75 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cStringIO import StringIO
+
+from casemgr import reports
+
+from tests import testcommon
+
+expect_forms = [
+    ('', '- Choose a form -'), 
+    ('sars_exposure', 'Exposure History (SARS)'), 
+    ('hospital_admit', 'Hospital admission'),
+]
+
+expect_headings = [
+    'Status', 'Local ID', 'Case Assignment', 'System ID', 
+    'Surname', 'Given names', 'Date of birth/Age', 'Sex', 
+    'Locality/Suburb', 'State', 'Onset Date', 'Tags',
+]
+
+linereport = '''\
+<?xml version="1.0"?>
+<report type="line" name="Test Report">
+ <syndrome>SARS</syndrome>
+ <export strip_newlines="yes" column_labels="fields" row_type="forms" />
+ <formdep name="sars_exposure" version="1" label="Exposure History (SARS)" />
+ <groups>
+  <group type="demog" label="Columns">
+   <column name="case_status" label="Status" />
+   <column name="local_case_id" label="Local ID" />
+   <column name="case_assignment" label="Case Assignment" />
+   <column name="case_id" label="System ID" />
+   <column name="surname" label="Surname" />
+   <column name="given_names" label="Given names" />
+   <column name="sex" label="Sex" />
+   <column name="locality" label="Locality/Suburb" />
+   <column name="state" label="State" />
+   <column name="onset_datetime" label="Onset Date" />
+   <column name="tags" label="Tags" />
+  </group>
+  <group type="demog" label="Additional information" />
+  <group type="form" form="sars_exposure" label="Exposure History (SARS)">
+   <column name="close_contact" label="Contact with case" />
+   <column name="contact_duration" label="Contact duration (hours)" />
+   <column name="contact_date_first" label="Date of first contact" />
+  </group>
+ </groups>
+ <ordering>
+  <orderby column="onset_datetime" direction="asc" />
+ </ordering>
+</report>
+'''
+
+class ReportTest(testcommon.AppTestCase):
+
+    def make_report(self, xml=None):
+        if xml is None:
+            xml = linereport
+        return reports.parse_file(2, StringIO(xml))
diff --git a/tests/reports/crosstab.py b/tests/reports/crosstab.py
new file mode 100644
index 0000000..5aacc89
--- /dev/null
+++ b/tests/reports/crosstab.py
@@ -0,0 +1,170 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import reports, messages
+
+from tests.reports import common
+from tests import testcommon
+
+crosstab = '''\
+<?xml version="1.0"?>
+<report type="crosstab" name="testreport">
+ <formdep name="sars_exposure" version="12" label="SARS Exp" />
+ <crosstab include_empty_pages="yes">
+  <axis name="row" type="demog" field="case_status" />
+  <axis name="column" type="demog" field="sex" />
+  <axis name="page" type="form" form="sars_exposure" field="Close_contact" />
+ </crosstab>
+</report>
+'''
+
+row_options = [(None, 'Missing'), ('!TOTAL!', 'TOTAL')]
+col_options = [('M', 'Male'), ('!TOTAL!', 'TOTAL')]
+page_options = [
+    ('True', 'Yes'), 
+    ('False', 'No'), 
+    ('Unknown', 'Unknown'), 
+    ('!TOTAL!', 'TOTAL')
+]
+
+cells_expect = [
+    [[1, 1], 
+     [1, 1]], 
+    [[1, 1], 
+     [1, 1]], 
+    [[1, 1], 
+     [1, 1]], 
+    [[3, 3], 
+     [3, 3]],
+]
+
+style_expect = [
+    [['', 'l'], 
+     ['t', 't l']], 
+    [['', 'l'], 
+     ['t', 't l']], 
+    [['', 'l'], 
+     ['t', 't l']], 
+    [['', 'l'], 
+     ['t', 't l']],
+]
+
+id_expect = [
+    [[[1], [1]], [[1], [1]]], 
+    [[[5], [5]], [[5], [5]]], 
+    [[[1], [1]], [[1], [1]]], 
+    [[[1, 5], [1, 5]], [[1, 5], [1, 5]]]
+]
+
+desc_expect = [
+    [['Status: Missing, Sex: Male, Contact with case: Yes', 
+      'Status: Missing, Contact with case: Yes'],
+     ['Sex: Male, Contact with case: Yes', 
+      'Contact with case: Yes']], 
+    [['Status: Missing, Sex: Male, Contact with case: No',
+      'Status: Missing, Contact with case: No'],
+     ['Sex: Male, Contact with case: No',
+      'Contact with case: No']],
+    [['Status: Missing, Sex: Male, Contact with case: Unknown',
+      'Status: Missing, Contact with case: Unknown'],
+     ['Sex: Male, Contact with case: Unknown',
+      'Contact with case: Unknown']],
+    [['Status: Missing, Sex: Male',
+      'Status: Missing'],
+     ['Sex: Male', '']]
+]
+
+demog_col_options = [
+    ('interpreter_req', 'Interpreter'),
+    ('case_status', 'Status'),
+    ('case_assignment', 'Case Assignment'),
+    ('sex', 'Sex'),
+    ('state', 'State'),
+    ('passport_country', 'Passport country/Nationality'),
+]
+
+class CrosstabReportTests(common.ReportTest):
+
+    def test_crosstab(self):
+        credentials = testcommon.DummyCredentials()
+        params = self.make_report(crosstab)
+        self.assertEqual(params.row.form_options(), [
+            ('none:', 'None'),
+            ('demog:', 'Demographic fields'),
+            ('form:sars_exposure', 'Exposure History (SARS)'),
+            ('form:hospital_admit', 'Hospital admission'),
+        ])
+        self.assertEqual(params.row.show_fields(), True)
+        self.assertEqual(params.col.show_fields(), True)
+        self.assertEqual(params.page.show_fields(), True)
+        self.assertListEq(params.row.col_options(), demog_col_options)
+        self.assertListEq(params.col.col_options(), demog_col_options)
+        self.assertListEq(params.page.col_options(),
+            [('close_contact', 'Contact with case')])
+        msgs = messages.Messages()
+        report = params.report(credentials, msgs)
+        self.assertEqual(report.row.options, row_options)
+        self.assertEqual(report.col.options, col_options)
+        self.assertEqual(report.page.options, page_options)
+        # Check counts
+        pages = []
+        for page_val, page_lab in report.page.options:
+            page = []
+            for row_val, row_label in report.row.options:
+                row = []
+                for col_val, col_label in report.col.options:
+                    key = row_val, col_val, page_val
+                    row.append(report.tally[key])
+                page.append(row)
+            pages.append(page)
+        self.assertEqual(pages, cells_expect)
+        # Check styling
+        pages = []
+        for page_val, page_lab in report.page.options:
+            page = []
+            for row_val, row_label in report.row.options:
+                row = []
+                for col_val, col_label in report.col.options:
+                    row.append(report.style(row_val, col_val))
+                page.append(row)
+            pages.append(page)
+        self.assertEqual(pages, style_expect)
+        # Case ID Lookup
+        pages = []
+        for page_idx in range(len(report.page.options)):
+            page = []
+            for row_idx in range(len(report.row.options)):
+                row = []
+                for col_idx in range(len(report.col.options)):
+                    coords = row_idx, col_idx, page_idx
+                    row.append(report.get_key_case_ids(*coords))
+                page.append(row)
+            pages.append(page)
+        self.assertEqual(pages, id_expect)
+        # Cell desc Lookup
+        pages = []
+        for page_idx in range(len(report.page.options)):
+            page = []
+            for row_idx in range(len(report.row.options)):
+                row = []
+                for col_idx in range(len(report.col.options)):
+                    coords = row_idx, col_idx, page_idx
+                    row.append(report.desc_key(*coords))
+                page.append(row)
+            pages.append(page)
+        self.assertEqual(pages, desc_expect)
diff --git a/tests/reports/export.py b/tests/reports/export.py
new file mode 100644
index 0000000..d79b463
--- /dev/null
+++ b/tests/reports/export.py
@@ -0,0 +1,70 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import reports
+
+from tests import testcommon
+from tests.reports import common
+
+class ExportTests(common.ReportTest):
+    export_forms_label_expect = [
+        ['Status', 'Local ID', 'Case Assignment', 'System ID', 'Surname', 'Given names', 'Sex', 'Locality/Suburb', 'State', 'Onset Date', 'Tags', 'Contact with case', 'Contact duration (hours)', 'Date of first contact'], 
+        [None, 'case_A', None, 1, 'Person_Surname_A', 'Person_Given_Name_A', 'Male', None, None, '02/04/2003 00:00:00', 'TagTwo', 'Yes', '23.1', '01/03/2003'], 
+        [None, 'case_A', None, 1, 'Person_Surname_A', 'Person_Given_Name_A', 'Male', None, None, '02/04/2003 00:00:00', 'TagTwo', 'Unknown', '29.1', '02/01/2001'], 
+        [None, 'case_B', None, 2, 'Person_Surname_B', 'Person_Given_Name_B', 'Male', None, None, '03/05/2003 00:00:00', 'TagOne TagTwo', None, None, None], 
+        [None, 'case_E_diff_unit_acl', None, 5, 'Person_Surname_B', 'Person_Given_Name_B', 'Male', None, None, '04/06/2003 00:00:00', None, 'No', '2.4', '02/04/2002']
+    ]
+    export_forms_dbcols_expect = list(export_forms_label_expect)
+    export_forms_dbcols_expect[0] = ['case_status', 'local_case_id', 'case_assignment', 'case_id', 'surname', 'given_names', 'sex', 'locality', 'state', 'onset_datetime', 'tags', 'sars_exposure.close_contact', 'sars_exposure.contact_duration', 'sars_exposure.contact_date_first']
+
+    def test_export_forms(self):
+        """
+        CSV report export (by form)
+        """
+        credentials = testcommon.DummyCredentials()
+        params = self.make_report()
+        params.export_row_type = 'forms'
+        params.export_column_labels = 'fields'
+        rows = list(params.export_rows(credentials))
+        self.assertEqual(rows, self.export_forms_label_expect)
+        params.export_column_labels = 'dbcols'
+        rows = list(params.export_rows(credentials))
+        self.assertEqual(rows, self.export_forms_dbcols_expect)
+
+    export_cases_label_expect = [
+        ['Status', 'Local ID', 'Case Assignment', 'System ID', 'Surname', 'Given names', 'Sex', 'Locality/Suburb', 'State', 'Onset Date', 'Tags', 'Contact with case (1)', 'Contact duration (hours) (1)', 'Date of first contact (1)', 'Contact with case (2)', 'Contact duration (hours) (2)', 'Date of first contact (2)'], 
+        [None, 'case_A', None, 1, 'Person_Surname_A', 'Person_Given_Name_A', 'Male', None, None, '02/04/2003 00:00:00', 'TagTwo', 'Yes', '23.1', '01/03/2003', 'Unknown', '29.1', '02/01/2001'], 
+        [None, 'case_B', None, 2, 'Person_Surname_B', 'Person_Given_Name_B', 'Male', None, None, '03/05/2003 00:00:00', 'TagOne TagTwo', None, None, None, None, None, None], 
+        [None, 'case_E_diff_unit_acl', None, 5, 'Person_Surname_B', 'Person_Given_Name_B', 'Male', None, None, '04/06/2003 00:00:00', None, 'No', '2.4', '02/04/2002', None, None, None]
+    ]
+    export_cases_dbcols_expect = list(export_cases_label_expect)
+    export_cases_dbcols_expect[0] = ['case_status', 'local_case_id', 'case_assignment', 'case_id', 'surname', 'given_names', 'sex', 'locality', 'state', 'onset_datetime', 'tags', 'sars_exposure.close_contact.1', 'sars_exposure.contact_duration.1', 'sars_exposure.contact_date_first.1', 'sars_exposure.close_contact.2', 'sars_exposure.contact_duration.2', 'sars_exposure.contact_date_first.2']
+
+    def test_export_cases(self):
+        """
+        CSV report export (by case)
+        """
+        credentials = testcommon.DummyCredentials()
+        params = self.make_report()
+        params.export_row_type = 'cases'
+        params.export_column_labels = 'fields'
+        rows = list(params.export_rows(credentials))
+        self.assertEqual(rows, self.export_cases_label_expect)
+        params.export_column_labels = 'dbcols'
+        rows = list(params.export_rows(credentials))
+        self.assertEqual(rows, self.export_cases_dbcols_expect)
diff --git a/tests/reports/filters.py b/tests/reports/filters.py
new file mode 100644
index 0000000..be370df
--- /dev/null
+++ b/tests/reports/filters.py
@@ -0,0 +1,465 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from itertools import izip
+from cStringIO import StringIO
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+from cocklebur import xmlwriter, dbobj, datetime
+
+from casemgr import globals, reports
+from casemgr.reports.reportfilters import *
+
+from tests import testcommon
+
+class DummyField:
+    name = 'dummy'
+    label = 'Dummy test field'
+    table = 'table'
+
+    def optionexpr(self):
+        return [
+            (1, 'Tag'),
+            ('AA', 'aa option'),
+            ('BB', 'bb option'),
+            ('CC', 'cc option'),
+        ]
+
+class DummyInput:
+    column = 'dummy'
+    label = 'Dummy test field'
+
+    choices = [
+            ('AA', 'aa option'),
+            ('BB', 'bb option'),
+            ('CC', 'cc option'),
+        ]
+
+
+class DummyParams:
+
+    def demog_fields(self):
+        class DF:
+            def field_by_name(self, name):
+                return DummyField()
+        return DF()
+
+    def form_info(self, name):
+        class FI:
+            name = 'testform'
+            label = 'Dummy form'
+            version = 2
+            def load(self):
+                class Form:
+                    class Columns:
+                        def find_input(self, name):
+                            return DummyInput()
+                    columns = Columns()
+                return Form()
+            def tablename(self):
+                return 'formtable'
+        return FI()
+
+class ReportFilterTermsTest(testcommon.SchemaTestCase):
+
+    # Not tested
+    #   DOBDemogFieldOps - relative dates tricky
+    #   NamesDemogFieldOps - brings in fuzzyperson logic
+
+    def exercise(self, term, field_ops, want_markup,
+                 want_desc, want_xml, want_query, 
+                 allow_negate=True, want_datefmt=None):
+        term.params = DummyParams()
+        term.field_ops = field_ops(term)
+        self.assertEqual(term.field, DummyField.name)
+        self.assertEqual(term.label(), DummyField.label)
+        self.assertEqual(term.get_markup(), want_markup)
+        self.assertEqual(term.allow_negate(), allow_negate)
+        if term.form:
+            self.assertEqual(term.form, 'testform')
+            self.assertEqual(term.form_label(), 'Dummy form')
+        else:
+            self.assertEqual(term.form_label(), 'Demographics')
+        self.assertEqual(term.desc(), want_desc)
+        self.assertEqual(term.date_format(), want_datefmt)
+        # XML
+        f = StringIO()
+        xmlgen = xmlwriter.XMLwriter(f)
+        term.to_xml(xmlgen)
+        got_xml = f.getvalue().replace('<?xml version="1.0"?>\n', '')
+        self.assertEqual(got_xml, want_xml)
+        # Query
+        query = dbobj.ExprBuilder(globals.db.get_table('cases'), 'AND')
+        term.query(query)
+        got_query = query.build_expr()[0]
+        self.assertEqual(got_query, want_query)
+
+    def test_pattern_demog_term(self):
+        # Pattern term
+        term = PatternTerm('dummy')
+        term.value = 'XX*'
+        self.exercise(term, PatternDemogFieldOps, 'pattern',
+                      want_desc="matching pattern 'XX*'",
+                      want_xml='<term field="dummy" op="pattern">\n <value>XX*'
+                               '</value>\n</term>\n',
+                      want_query='(table.dummy ILIKE %s)')
+        # Now try it negated
+        term.negate = True
+        self.exercise(term, PatternDemogFieldOps, 'pattern',
+                      want_desc="not matching pattern 'XX*'",
+                      want_xml='<term field="dummy" op="pattern" negate="yes">'
+                               '\n <value>XX*</value>\n</term>\n',
+                      want_query='(NOT (table.dummy ILIKE %s))')
+        # And with multiple patterns
+        term.value = 'XX*, YY*'
+        self.exercise(term, PatternDemogFieldOps, 'pattern',
+                      want_desc="not matching pattern 'XX*, YY*'",
+                      want_xml='<term field="dummy" op="pattern" negate="yes">'
+                               '\n <value>XX*, YY*</value>\n</term>\n',
+                      want_query='(NOT ((table.dummy ILIKE %s OR table.dummy '
+                                 'ILIKE %s)))')
+
+    def test_pattern_form_term(self):
+        term = PatternTerm('dummy', form='testform')
+        term.value = 'XX*'
+        self.exercise(term, PatternFormFieldOps, 'pattern',
+                      want_desc="matching pattern 'XX*'",
+                      want_xml='<term form="testform" field="dummy" '
+                               'op="pattern">\n <value>XX*</value>\n</term>\n',
+                      want_query='(formtable.dummy ILIKE %s)')
+        term.negate = True
+        self.exercise(term, PatternFormFieldOps, 'pattern',
+                      want_desc="not matching pattern 'XX*'",
+                      want_xml='<term form="testform" field="dummy" '
+                               'op="pattern" negate="yes">\n '
+                               '<value>XX*</value>\n</term>\n',
+                      want_query='(NOT (formtable.dummy ILIKE %s))')
+
+    def test_range_demog_term(self):
+        # Range term, lower limit
+        term = RangeTerm('dummy')
+        term.from_value = 'XX'
+        self.exercise(term, RangeDemogFieldOps, 'range',
+                      want_desc="from XX (inclusive)",
+                      want_xml='<term field="dummy" op="range">\n '
+                               '<from inclusive="yes">XX</from>\n</term>\n',
+                      want_query='(table.dummy >= %s)')
+        # Add an upper limit
+        term.to_value = 'YY'
+        self.exercise(term, RangeDemogFieldOps, 'range',
+                      want_desc="from XX (inclusive) to YY",
+                      want_xml='<term field="dummy" op="range">\n '
+                               '<from inclusive="yes">XX</from>\n '
+                               '<to>YY</to>\n</term>\n',
+                      want_query='(table.dummy >= %s AND table.dummy < %s)')
+        # Negation
+        term.negate = True
+        self.exercise(term, RangeDemogFieldOps, 'range',
+                      want_desc="not from XX (inclusive) to YY",
+                      want_xml='<term field="dummy" op="range" negate="yes">\n '
+                               '<from inclusive="yes">XX</from>\n '
+                               '<to>YY</to>\n</term>\n',
+                      want_query='(NOT (table.dummy >= %s AND table.dummy < %s))')
+
+    def test_date_range_form_term(self):
+        # Range term, lower limit
+        term = RangeTerm('dummy', form='testform')
+        term.from_value = '2009-5-1'
+        term.incl_from = False
+        self.exercise(term, DateRangeFormFieldOps, 'range',
+                      want_datefmt='%d/%m/%Y',
+                      want_desc="from 2009-5-1",
+                      want_xml='<term form="testform" field="dummy" op="range">'
+                               '\n <from>2009-05-01</from>\n</term>\n',
+                      want_query='(formtable.dummy > %s)')
+        # Add an upper limit
+        term.to_value = '2010-6-1'
+        term.incl_to = True
+        self.exercise(term, DateRangeFormFieldOps, 'range',
+                      want_datefmt='%d/%m/%Y',
+                      want_desc="from 2009-5-1 to 2010-6-1 (inclusive)",
+                      want_xml='<term form="testform" field="dummy" '
+                               'op="range">\n <from>2009-05-01</from>\n '
+                               '<to inclusive="yes">2010-06-01</to>\n</term>\n',
+                      want_query='(formtable.dummy > %s AND '
+                                 'formtable.dummy <= %s)')
+
+    def test_in_demog_term(self):
+        term = InTerm('dummy')
+        term.values = ['AA', 'BB']
+        self.exercise(term, InDemogFieldOps, 'checkboxes',
+                      want_desc="is in aa option, bb option",
+                      want_xml='<term field="dummy" op="in">\n <value>AA'
+                               '</value>\n <value>BB</value>\n</term>\n',
+                      want_query='(table.dummy IN (%s,%s))')
+        term.values = ['?', 'AA', 'BB']
+        self.exercise(term, InDemogFieldOps, 'checkboxes',
+                      want_desc="is in ?, aa option, bb option",
+                      want_xml='<term field="dummy" op="in">\n <value>?'
+                               '</value>\n <value>AA</value>\n <value>BB'
+                               '</value>\n</term>\n',
+                      want_query='((table.dummy IS NULL OR table.dummy '
+                                 'IN (%s,%s)))')
+        term.values = ['AA']
+        term.negate = True
+        self.exercise(term, InDemogFieldOps, 'checkboxes',
+                      want_desc="is not aa option",
+                      want_xml='<term field="dummy" op="in" negate="yes">\n '
+                               '<value>AA</value>\n</term>\n',
+                      want_query='(NOT (table.dummy IN (%s)))')
+
+    def test_in_choices_form_term(self):
+        term = InTerm('dummy', form='testform')
+        term.values = ['AA', 'BB']
+        self.exercise(term, ChoicesFormFieldOps, 'checkboxes',
+                      want_desc="is in aa option, bb option",
+                      want_xml='<term form="testform" field="dummy" op="in">\n <value>AA</value>\n <value>BB</value>\n</term>\n',
+                      want_query='(formtable.dummy IN (%s,%s))')
+
+    def test_in_checkbox_form_term(self):
+        term = InTerm('dummy', form='testform')
+        term.values = ['AA', 'BB']
+        self.exercise(term, CheckboxFormFieldOps, 'checkboxes',
+                      want_desc="is in aa option, bb option",
+                      want_xml='<term form="testform" field="dummy" op="in">\n <value>AA</value>\n <value>BB</value>\n</term>\n',
+                      want_query='((formtable.dummyaa OR formtable.dummybb))')
+
+    def test_select_deleted_term(self):
+        term = SelectTerm('dummy')
+        term.value = 'exclude'
+        self.exercise(term, DeletedDemogFieldOps, 'select',
+                      allow_negate=False,
+                      want_desc="is Excluded",
+                      want_xml='<term field="dummy" op="select">\n '
+                               '<value>exclude</value>\n</term>\n',
+                      want_query='(NOT cases.deleted)')
+        term.value = 'both'
+        self.exercise(term, DeletedDemogFieldOps, 'select',
+                      allow_negate=False,
+                      want_desc="is Included",
+                      want_xml='<term field="dummy" op="select">\n '
+                               '<value>both</value>\n</term>\n',
+                      want_query='True')
+        term.value = 'only'
+        self.exercise(term, DeletedDemogFieldOps, 'select',
+                      allow_negate=False,
+                      want_desc="is Only deleted",
+                      want_xml='<term field="dummy" op="select">\n '
+                               '<value>only</value>\n</term>\n',
+                      want_query='(cases.deleted)')
+
+    def test_select_tag_term(self):
+        term = SelectTerm('dummy')
+        self.exercise(term, TagsDemogFieldOps, 'select',
+                      want_desc="is None",
+                      want_xml='<term field="dummy" op="select" />\n',
+                      want_query='True')
+        term.value = '1'
+        self.exercise(term, TagsDemogFieldOps, 'select',
+                      want_desc="is Tag",
+                      want_xml='<term field="dummy" op="select">\n <value>1'
+                               '</value>\n</term>\n',
+                      want_query='(cases.case_id IN (SELECT case_id FROM '
+                                 'case_tags JOIN tags USING (tag_id) '
+                                 'WHERE (tag ILIKE %s)))')
+
+    def test_caseset_term(self):
+        term = CasesetTerm([1,2,3,9], 'Four cases', field='dummy')
+        self.exercise(term, CasesetDemogFieldOps, 'caseset',
+                      want_desc="Case set: Four cases (4 cases)",
+                      want_xml='<term field="dummy" op="caseset" caseset="Four '
+                               'cases">\n <commalist>1,2,3,9</commalist>\n'
+                               '</term>\n',
+                      want_query='(case_id IN (%s,%s,%s,%s))')
+
+
+class FilterAdderTest(testcommon.AppTestCase):
+
+    want_groups = [
+        ('demog', 'Demographics'),
+        ('andsubexpr', 'AND Subexpression'),
+        ('orsubexpr', 'OR Subexpression'),
+        ('sars_exposure', 'Exposure History (SARS)'),
+        ('hospital_admit', 'Hospital admission')
+    ]
+
+    want_avail = [
+        ('demog', [
+            ('', '- Choose filter -'),
+            ('case_assignment', 'Case Assignment'),
+            ('data_src', 'Data source'),
+            ('DOB', 'Date of birth/Age'),
+            ('deleted', 'Deleted'),
+            ('delete_timestamp', 'Deletion date'),
+            ('delete_reason', 'Deletion reason'),
+            ('fax_phone', 'Fax'),
+            ('given_names', 'Given names'),
+            ('home_phone', 'Home phone'),
+            ('interpreter_req', 'Interpreter'),
+            ('local_case_id', 'Local ID'),
+            ('locality', 'Locality/Suburb'),
+            ('mobile_phone', 'Mobile phone'),
+            ('notification_datetime', 'Notification Date'),
+            ('onset_datetime', 'Onset Date'),
+            ('passport_country', 'Passport country/Nationality'),
+            ('passport_number', 'Passport number'),
+            ('postcode', 'Postcode'),
+            ('sex', 'Sex'),
+            ('state', 'State'),
+            ('case_status', 'Status'),
+            ('street_address', 'Street address'),
+            ('surname', 'Surname'),
+            ('case_id', 'System ID'),
+            ('tags', 'Tags'),
+            ('work_phone', 'Work/School phone'),
+        ]),
+        ('sars_exposure', [
+            ('', '- Choose filter -'),
+            ('contact_duration', 'Contact duration (hours)'),
+            ('close_contact', 'Contact with case'),
+            ('contact_date_first', 'Date of first contact'),
+            ('contact_favourite_food', 'Favourite foods'),
+            ('contact_date_last', 'Most recent contact'),
+        ]),
+        ('hospital_admit', [
+            ('', '- Choose filter -'),
+            ('admission_aerosol', 'admission_aerosol'),
+            ('admission_aerosol_detail', 'admission_aerosol_detail'),
+            ('admission_co_morb', 'admission_co_morb'),
+            ('admission_hospital', 'admission_hospital'),
+            ('admission_icu', 'admission_icu'),
+            ('admission_icu_stay', 'admission_icu_stay'),
+            ('admission_isolation', 'admission_isolation'),
+            ('admission_mech_vent', 'admission_mech_vent'),
+            ('admission_stay', 'admission_stay'),
+            ('admission_date', 'admitted'),
+            ('admission_discharge_date', 'discharged'),
+            ('admission_hospitalised', 'hospitalised'),
+        ])]
+
+    def test_adder(self):
+        params = reports.new_report(2)
+        self.assertEqual(params.filter.children, [])
+        # Test adder cancel
+        adder = params.filter_adder(params.filter)
+        adder.abort()
+        self.assertEqual(params.filter.children, [])
+        # Test add demog field
+        adder = params.filter_adder(params.filter)
+        self.assertEqual(adder.groups(), self.want_groups)
+        available = [(group.name, group.available_filters())
+                     for group in adder if group.has_field]
+        self.assertEqual(available, self.want_avail)
+        adder.group = 'demog'
+        self.failIf(adder.is_complete())
+        self.failUnless(adder.has_field())
+        self.assertEqual(adder.fields(), self.want_avail[0][1])
+        adder.field = 'surname'
+        self.failIf(adder.is_complete())
+        self.assertEqual(adder.field_ops(), ['pattern', 'phonetic'])
+        adder.op = 'pattern'
+        self.failUnless(adder.is_complete())
+        surname = adder.add()
+        self.assertEqual(params.filter.children, [surname])
+        # Test add "OR" expr
+        adder = params.filter_adder(params.filter)
+        adder.group = 'orsubexpr'
+        self.failIf(adder.has_field())
+        self.failUnless(adder.is_complete())
+        orexpr = adder.add()
+        self.assertEqual(params.filter.children, [surname, orexpr])
+        # Test subexpr add
+        adder = params.filter_adder(orexpr)
+        adder.group = 'sars_exposure'
+        self.failIf(adder.is_complete())
+        self.failUnless(adder.has_field())
+        self.assertEqual(adder.fields(), self.want_avail[1][1])
+        adder.field = 'close_contact'
+        self.failUnless(adder.is_complete())
+        ccontact = adder.add()
+        self.assertEqual(params.filter.children, [surname, orexpr])
+        self.assertEqual(orexpr.children, [ccontact])
+        # While we are here, test deletion (and implicit trimming)
+        params.del_filter(ccontact)
+        self.assertEqual(params.filter.children, [surname])
+
+    want_query = (
+        'SELECT cases.* FROM cases '
+         'WHERE (cases.case_id IN '
+           '(SELECT cases.case_id FROM cases '
+             'JOIN persons USING (person_id) '
+             'LEFT JOIN (SELECT case_id, form_hospital_admit_00001.* '
+                    'FROM form_hospital_admit_00001 '
+                    'JOIN case_form_summary USING (summary_id) '
+                    'WHERE NOT deleted) '
+                'AS form_hospital_admit_00001 USING (case_id) '
+             'WHERE (syndrome_id = %s '
+              'AND (cases.case_status IN (%s,%s) '
+               'OR form_hospital_admit_00001.admission_hospitalised IN (%s)))))'
+        )
+
+    def test_query(self):
+        params = reports.new_report(2)
+        params.caseset_filter([1,2,3,9], 'Four cases')
+        f_status = params.make_term(op='in', field='case_status', 
+                                    values=['confirmed', 'suspected'])
+        f_hosp = params.make_term(op='in', field='admission_hospitalised', 
+                                  form='hospital_admit', values=['True'])
+        f_or = params.make_term(op='or')
+        params.add_filter(f_or)
+        f_or.add_filter(f_status)
+        f_or.add_filter(f_hosp)
+        query = globals.db.query('cases')
+        params.filter_query(query)
+        got_query = query.build_expr()[0]
+        self.assertEqual(got_query, self.want_query)
+
+
+class FiltersWalkTest(testcommon.TestCase):
+    
+    def test_yield_nodes(self):
+        class Node: pass
+        root = Node()
+        root.children = [Node(), Node()]
+        root.children[0].children = [Node(), Node()]
+        tokens = [(t, p) for t, p, n in reports.reportfilters.yield_nodes(root)]
+        self.assertEqual(tokens,
+            [('open', ''),
+              ('open', '0'),
+               ('clause', '0.0'),
+               ('clause', '0.1'),
+              ('close', '0'),
+               ('clause', '1'),
+              ('close', ''),
+            ])
+        root = Node()
+        root.children = [Node(), Node()]
+        root.children[1].children = [Node(), Node()]
+        tokens = [(t, p) for t, p, n in reports.reportfilters.yield_nodes(root)]
+        self.assertEqual(tokens, 
+            [('open', ''),
+              ('clause', '0'),
+              ('open', '1'),
+               ('clause', '1.0'),
+               ('clause', '1.1'),
+              ('close', '1'),
+             ('close', '')
+            ])
diff --git a/tests/reports/linereport.py b/tests/reports/linereport.py
new file mode 100644
index 0000000..a7df796
--- /dev/null
+++ b/tests/reports/linereport.py
@@ -0,0 +1,66 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+from itertools import izip
+
+from casemgr import reports, messages
+
+from tests.reports import common
+from tests import testcommon
+
+expectrows = [
+    ([None, 'case_A', None, 1, 
+      'Person_Surname_A', 'Person_Given_Name_A', 'Male', 
+      None, None, '02/04/2003 00:00:00', 'TagTwo'],
+     [('F11', 'Exposure History (SARS) F11', 
+       'Contact with case: Yes, Contact duration (hours): 23.1, '
+       'Date of first contact: 01/03/2003'),
+      ('F44', 'Exposure History (SARS) F44', 
+       'Contact with case: Unknown, Contact duration (hours): 29.1, '
+       'Date of first contact: 02/01/2001')]),
+    ([None, 'case_B', None, 2, 
+      'Person_Surname_B', 'Person_Given_Name_B', 'Male', 
+      None, None, '03/05/2003 00:00:00', 'TagOne TagTwo'],
+     []),
+    ([None, 'case_E_diff_unit_acl', None, 5, 
+      'Person_Surname_B', 'Person_Given_Name_B', 'Male', 
+      None, None, '04/06/2003 00:00:00', None],
+     [('F33', 'Exposure History (SARS) F33', 
+       'Contact with case: No, Contact duration (hours): 2.4, '
+       'Date of first contact: 02/04/2002')]),
+]
+
+class LineReportTests(common.ReportTest):
+
+    def test_report(self):
+        """
+        report generation
+        """
+        credentials = testcommon.DummyCredentials()
+        params = self.make_report()
+        self.assertEqual(params.get_case_ids(credentials), [1, 2, 5])
+        self.assertEqual(params.count(credentials), 3)
+        msgs = messages.Messages()
+        chunker = params.report(credentials, msgs)
+        report_headings = list(common.expect_headings)
+        report_headings.remove('Date of birth/Age')
+        self.assertEqual(chunker.headings(), report_headings)
+        result = list(chunker)
+        self.assertEqual(len(result), len(expectrows))
+        for got, (col_want, text_want) in izip(result,expectrows):
+            self.assertEqual(got.columns, col_want)
+            self.assertEqual(got.freetext, text_want)
diff --git a/tests/reports/orderby.py b/tests/reports/orderby.py
new file mode 100644
index 0000000..e0bb877
--- /dev/null
+++ b/tests/reports/orderby.py
@@ -0,0 +1,69 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from casemgr import reports
+
+from tests import testcommon
+
+expect_ordercols = [
+    ('interpreter_req', 'Interpreter'),
+    ('case_status', 'Status'),
+    ('local_case_id', 'Local ID'),
+    ('case_assignment', 'Case Assignment'),
+    ('case_id', 'System ID'),
+    ('surname', 'Surname'),
+    ('given_names', 'Given names'),
+    ('DOB', 'Date of birth/Age'),
+    ('sex', 'Sex'),
+    ('home_phone', 'Home phone'),
+    ('mobile_phone', 'Mobile phone'),
+    ('fax_phone', 'Fax'),
+    ('street_address', 'Street address'),
+    ('locality', 'Locality/Suburb'),
+    ('state', 'State'),
+    ('postcode', 'Postcode'),
+    ('work_phone', 'Work/School phone'),
+    ('passport_number', 'Passport number'),
+    ('passport_country', 'Passport country/Nationality'),
+    ('notification_datetime', 'Notification Date'),
+    ('onset_datetime', 'Onset Date'),
+    ('tags', 'Tags'),
+    ('deleted', 'Deleted'),
+    ('delete_reason', 'Deletion reason'),
+    ('delete_timestamp', 'Deletion date'),
+]
+
+class ReportOrderbyTests(testcommon.AppTestCase):
+
+    def test_order(self):
+        """
+        order by column parameter handling
+        """
+        params = reports.new_report(2, 'line')
+        self.assertEqual(len(params.order_by), 0)
+        self.assertEqual(params.query_order(), '')
+        params.add_order()
+        params.order_by[0].col = 'onset_datetime'
+        params.order_by[0].rev = 'desc'
+        params.add_order()
+        params.order_by[1].col = 'surname'
+        self.assertEqual(params.query_order(), 
+                         'onset_datetime desc,surname asc')
+        params.del_order(0)
+        self.assertEqual(params.query_order(), 'surname asc')
+        self.assertEqual(params.order_cols(), expect_ordercols)
diff --git a/tests/reports/savenload.py b/tests/reports/savenload.py
new file mode 100644
index 0000000..59f81db
--- /dev/null
+++ b/tests/reports/savenload.py
@@ -0,0 +1,210 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from cStringIO import StringIO
+
+from casemgr import reports
+from casemgr.syndrome import SyndFormInfo
+from casemgr.reports import ReportParseError
+from casemgr.reports import xmlload
+from casemgr.reports.report import ReportParamsBase
+
+from tests.reports import common
+from tests import testcommon
+
+class ReportSaveNLoadTest(testcommon.TestCase):
+
+    def expecterr(self, exc, xml):
+        self.assertRaises(exc, reports.parse_file, None, StringIO(xml))
+
+    def roundtrip(self, xml_in):
+        syndrome_info = {
+            'sars_exposure': SyndFormInfo('sars_exposure', 'SARS Exp', 12),
+            'hospital_admit': SyndFormInfo('hospital_admit', 'Hosp Adm', 8),
+        }
+        saved = ReportParamsBase.form_info, ReportParamsBase.all_form_info
+        ReportParamsBase.form_info = syndrome_info.get
+        ReportParamsBase.all_form_info = syndrome_info.items
+        try:
+            params = xmlload.xmlload(StringIO(xml_in))
+            f = StringIO()
+            params.xmlsave(f)
+            xml_out = f.getvalue()
+            self.assertEqLines(xml_in, xml_out)
+        finally:
+            ReportParamsBase.form_info, ReportParamsBase.all_form_info = saved
+
+    def test_error_handling(self):
+        self.expecterr(ReportParseError, '<report />')
+        self.expecterr(KeyError, '<report name="" type="linereport" />')
+
+    def test_simple_line(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="line" name="xxx">
+ <export strip_newlines="yes" column_labels="fields" row_type="forms" />
+ <groups />
+</report>
+''')
+
+    def test_filter(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="line" name="xxx">
+ <export strip_newlines="yes" column_labels="fields" row_type="forms" />
+ <formdep name="sars_exposure" version="12" label="SARS Exp" />
+ <filter op="and">
+  <term form="sars_exposure" field="contact_favourite_food" op="in">
+   <value>AA</value>
+  </term>
+  <term field="local_case_id" op="pattern">
+   <value>AA</value>
+  </term>
+  <term field="onset_datetime" op="range">
+   <from inclusive="yes">2009-06-05 00:00:00</from>
+   <to inclusive="yes">2009-06-12 00:00:00</to>
+  </term>
+  <term field="case_id" op="caseset" caseset="Foo">
+   <commalist>1,2,3,4</commalist>
+  </term>
+ </filter>
+ <groups />
+</report>
+''')
+
+    def test_line(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="line" name="xxx">
+ <export strip_newlines="yes" column_labels="fields" row_type="forms" />
+ <formdep name="sars_exposure" version="12" label="SARS Exp" />
+ <groups>
+  <group type="demog" label="Columns">
+   <column name="case_id" label="System ID" />
+   <column name="case_status" label="Status" />
+   <column name="surname" label="Surname" />
+  </group>
+  <group type="form" form="sars_exposure" label="SARS Exposure">
+   <column name="close_contact" label="Close contact" />
+   <column name="contact_duration" label="Contact duration" />
+   <column name="contact_date_first" label="First contact" />
+  </group>
+ </groups>
+ <ordering>
+  <orderby column="surname" direction="asc" />
+  <orderby column="given_names" direction="asc" />
+ </ordering>
+</report>
+''')
+
+    def test_crosstab(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="crosstab" name="testreport">
+ <formdep name="sars_exposure" version="12" label="SARS Exp" />
+ <crosstab include_empty_pages="yes">
+  <axis name="row" type="demog" field="case_status" />
+  <axis name="column" type="demog" field="sex" />
+  <axis name="page" type="form" form="sars_exposure" field="contact_favourite_food" />
+ </crosstab>
+</report>
+''')
+
+    def test_epicurve(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="epicurve" name="testreport">
+ <formdep name="sars_exposure" version="12" label="SARS Exp" />
+ <epicurve missing_forms="no" format="png" nbins="D1">
+  <dates field="notification_datetime" />
+  <dates field="onset_datetime" />
+  <stacking form="sars_exposure" field="contact_favourite_food" ratios="no">
+   <suppress>False</suppress>
+  </stacking>
+ </epicurve>
+</report>
+''')
+
+    def test_contactvis(self):
+        self.roundtrip('''\
+<?xml version="1.0"?>
+<report type="contactvis" name="testreport">
+ <contactvis format="png" labelwith="surname,given_names" vismode="None" />
+</report>
+''')
+
+
+class ReportSharingTest(testcommon.AppTestCase):
+
+    def test_sharing(self):
+        """
+        saving, loading and sharing semantics
+        """
+        def _menu(cred, private=[], unit=[], public=[], quick=[]):
+            opts = reports.ReportMenu(cred, 2, 'line').by_sharing
+            self.assertEqual([i.label for i in opts['private']], private)
+            self.assertEqual([i.label for i in opts['unit']], unit)
+            self.assertEqual([i.label for i in opts['public']], public)
+            self.assertEqual([i.label for i in opts['quick']], quick)
+        cred_user = testcommon.DummyCredentials()
+        cred_unit = testcommon.DummyCredentials(user_id=2)
+        cred_other = testcommon.DummyCredentials(user_id=2,unit_id=2)
+        params = reports.new_report(2, 'line')
+        params.label = 'Test Save'
+        # Only visible to us
+        params.sharing = 'private'
+        params.save(cred_user)
+        _menu(cred_user, private=['Test Save'])
+        _menu(cred_unit)
+        _menu(cred_other)
+        # Visible to our unit
+        params.sharing = 'unit'
+        params.save(cred_user)
+        _menu(cred_user, unit=['Test Save'])
+        _menu(cred_unit, unit=['Test Save'])
+        _menu(cred_other)
+        # Visible to all
+        params.sharing = 'public'
+        params.save(cred_user)
+        _menu(cred_user, public=['Test Save'])
+        _menu(cred_unit, public=['Test Save'])
+        _menu(cred_other, public=['Test Save'])
+        # Visible to all & on home page (quick reports)
+        reports.reports_cache.load()
+        qr = reports.reports_cache.get_synd_unit(2, 1)
+        self.assertEqual([r.label for r in qr], [])
+        params.sharing = 'quick'
+        params.save(cred_user)
+        _menu(cred_user, quick=['Test Save'])
+        _menu(cred_unit, quick=['Test Save'])
+        _menu(cred_other, quick=['Test Save'])
+        reports.reports_cache.load()
+        qr = reports.reports_cache.get_synd_unit(2, 1)
+        self.assertEqual([r.label for r in qr], ['Test Save'])
+        # Loading
+        loaded_params = reports.load(params.loaded_from_id, cred_user)
+        self.failIf(loaded_params.check().have_errors())
+        reports.delete(loaded_params.loaded_from_id)
+        _menu(cred_user)
+        _menu(cred_unit)
+        _menu(cred_other)
+        # "last" autosave
+        params.autosave(cred_user)
+        _menu(cred_user, private=['Most recent: Test Save']) 
+        _menu(cred_unit)
+        _menu(cred_other)
diff --git a/tests/searchacl.py b/tests/searchacl.py
new file mode 100644
index 0000000..2af0188
--- /dev/null
+++ b/tests/searchacl.py
@@ -0,0 +1,117 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import unittest
+
+from cocklebur import dbobj
+
+import testcommon
+
+from casemgr import caseaccess
+
+#dbobj.execute_debug(1)
+
+
+class Cred:
+
+    def __init__(self, unit_id):
+        class _Unit:
+            def __init__(self, unit_id):
+                self.unit_id = unit_id
+        class _User:
+            def __init__(self, user_id):
+                self.user_id = user_id
+        if unit_id:
+            self.unit = _Unit(unit_id)
+            self.rights = ()
+        else:
+            self.rights = ('ACCESSALL',)
+        self.user = _User(0)
+
+
+class Case(testcommon.DBTestCase):
+
+    def setUp(self):
+        td = self.new_table('cases')
+        td.column('case_id', dbobj.SerialColumn, primary_key=True)
+        td.column('syndrome_id', dbobj.IntColumn)
+        td.column('deleted', dbobj.BooleanColumn)
+        td.create()
+
+        td = self.new_table('case_acl')
+        td.column('case_id', dbobj.ReferenceColumn, references = 'cases')
+        td.column('unit_id', dbobj.IntColumn)
+        td.create()
+
+        td = self.new_table('workqueues')
+        td.column('queue_id', dbobj.SerialColumn, primary_key=True)
+        td.column('unit_id', dbobj.IntColumn)
+        td.column('user_id', dbobj.IntColumn)
+        td.create()
+
+        td = self.new_table('workqueue_members')
+        td.column('queue_id', dbobj.ReferenceColumn, references = 'workqueues')
+        td.column('unit_id', dbobj.IntColumn)
+        td.column('user_id', dbobj.IntColumn)
+        td.create()
+
+        td = self.new_table('tasks')
+        td.column('task_id', dbobj.SerialColumn, primary_key=True)
+        td.column('case_id', dbobj.ReferenceColumn, references = 'cases')
+        td.column('queue_id', dbobj.ReferenceColumn, references = 'workqueues')
+        td.create()
+
+        curs = self.db.cursor()
+        # Case 1, syndrome 1, unit 1 & 3
+        dbobj.execute(curs, 'INSERT INTO cases VALUES (1, 1, false)')
+        dbobj.execute(curs, 'INSERT INTO case_acl VALUES (1, 1)')
+        dbobj.execute(curs, 'INSERT INTO case_acl VALUES (1, 3)')
+        self.db.commit()
+
+        # Case 4, syndrome 1, no unit
+        dbobj.execute(curs, 'INSERT INTO cases VALUES (4, 1, false)')
+
+        # Case 5, syndrome 2, unit 1
+        dbobj.execute(curs, 'INSERT INTO cases VALUES (5, 2, false)')
+        dbobj.execute(curs, 'INSERT INTO case_acl VALUES (5, 1)')
+
+    # XXX Need to test access via tasks
+    def runTest(self):
+        def _test(cred, expect):
+            query = self.db.query('cases')
+            caseaccess.acl_query(query, cred)
+            self.assertEqual(query.fetchcols('case_id'), expect)
+
+        _test(Cred(None), [1, 4, 5])
+        _test(Cred(1),    [1, 5])
+        _test(Cred(2),    [])
+
+
+class Suite(unittest.TestSuite):
+    test_list = (
+        'runTest',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(Case, self.test_list))
+
+def suite():
+    return Suite()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tests/testcommon.py b/tests/testcommon.py
new file mode 100644
index 0000000..3c32a92
--- /dev/null
+++ b/tests/testcommon.py
@@ -0,0 +1,238 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import os, sys
+import csv
+import time
+import random
+import unittest
+
+from cocklebur import dbobj
+from casemgr.notification.client import dummy_notification_client
+
+
+def freeze_time(t, fn, *a, **kw):
+    import mx
+    saved_now = mx.DateTime.now
+    mx.DateTime.now = lambda : t
+    try:
+        return fn(*a, **kw)
+    finally:
+        mx.DateTime.now = saved_now
+
+
+def randomDSN():
+    name = ['unittest']
+    for n in xrange(6):
+        name.append(chr(random.randint(ord('a'), ord('z'))))
+    return dbobj.DSN(database=''.join(name))
+
+test_dsn = randomDSN()
+destroy_db_atexit = True
+
+def set_dsn(dsn):
+    global test_dsn, destroy_db_atexit
+    test_dsn = dbobj.DSN(dsn)
+    destroy_db_atexit = False
+
+if 'casemgr.globals' not in sys.modules:
+    class DummyGlobals(object): 
+        notify = dummy_notification_client()
+        class Error(Exception): pass
+    import casemgr
+    sys.modules['casemgr.globals'] = casemgr.globals = DummyGlobals()
+
+
+class DummyCredentials:
+
+    def __init__(self, unit_id=1, user_id=1):
+        class CredUnitUser:
+            def __init__(self, **kwargs):
+                self.__dict__.update(kwargs)
+        self.unit = CredUnitUser(unit_id=unit_id)
+        self.user = CredUnitUser(user_id=user_id)
+        self.rights = []
+
+
+class DummySyndrome:
+
+    def __init__(self):
+        self.syndrome_id = 2
+
+
+def load_table_from_csv(db, table, datadir, csvfile):
+    """
+    Load a table from a CSV file, with the first row of the CSV file
+    naming the columns.
+    """
+    csvfile = os.path.join(datadir, csvfile + '.csv')
+    reader = iter(csv.reader(open(csvfile)))
+    # First row is column names
+    cols = [name.strip() for name in reader.next()]
+    for row in reader:
+        dbrow = db.new_row(table)
+        for col, value in zip(cols, row):
+            setattr(dbrow, col, value)
+        dbrow.db_update(refetch=False)
+
+def _clean_db():
+    from casemgr import globals
+    if hasattr(globals, 'db'):
+        print 'Destroying scratch db %s' % globals.db.dsn
+        globals.db.rollback()
+        globals.db.drop_database(globals.db)
+        globals.db.close()
+        del globals.db
+
+
+def _connect_db():
+    from casemgr import globals
+    try:
+        globals.db
+    except AttributeError:
+        import atexit
+        if destroy_db_atexit:
+            print 'Creating scratch db %s ...' % test_dsn,
+        else:
+            print 'Using db %s ...' % test_dsn,
+        sys.stdout.flush()
+        globals.db = dbobj.DatabaseDescriber(test_dsn)
+        globals.db.create()
+        if destroy_db_atexit:
+            atexit.register(_clean_db)
+        print 'done'
+    return globals.db
+
+
+class TestCase(unittest.TestCase):
+
+    def assertListEq(self, a, b, msg=None):
+        if a != b:
+            import difflib
+            a = [repr(aa) for aa in a]
+            b = [repr(bb) for bb in b]
+            diff = list(difflib.unified_diff(a, b, lineterm=''))
+            diff = '\n'.join(diff[2:])
+            if msg:
+                diff = '%s\n%s' % (msg, diff)
+            raise AssertionError(diff)
+
+    def assertEqLines(self, a, b, msg=None):
+        self.assertListEq(a.splitlines(), b.splitlines(), msg)
+
+
+class DBTestCase(TestCase):
+
+    def __init__(self, *args):
+        unittest.TestCase.__init__(self, *args)
+        self.module_path = os.path.dirname(__file__)
+        self.data_path = os.path.join(self.module_path, 'data')
+        self.table_path = os.path.join(self.data_path, 'tables')
+        self.db = _connect_db()
+
+    def tearDown(self):
+        from casemgr import globals
+        self.db.rollback()
+        self.db.drop_all_tables(self.db)
+
+    def new_table(self, tablename):
+        return self.db.new_table(tablename)
+
+    def load_table(self, tablename, filename=None):
+        if filename is None:
+            filename = tablename
+        load_table_from_csv(self.db, tablename, self.table_path, filename)
+
+    def psql(self):
+        self.db.commit()
+        os.system('psql %s' % self.db.dsn.database)
+        #dbobj.execute_debug(1)
+
+
+class SchemaTestCase(DBTestCase):
+
+    """
+    TestCase subclass for tests which need a database describer (schema)
+    in place, but do not actually touch the database (typically those
+    tests that only want to see what queries will be generated).
+    """
+    def setUp(self):
+        from casemgr.schema import schema
+        schema.define_db(test_dsn)
+
+class AppTestCase(DBTestCase):
+
+    """
+    Test class for tests that need a more fully populated application
+    schema. This takes a lot longer to set up, so we don't use it for the
+    general case.
+    """
+    # Tables need to be loaded in a specific order
+    load_tables = (
+        'users', 'units',
+        'forms',
+        'syndrome_types', 'syndrome_forms',
+        'persons', 'cases',
+        'case_acl', 'case_form_summary',
+        'tags', 'case_tags',
+        'address_states', 'syndrome_case_assignments', 'syndrome_case_status',
+    )
+    # Tables not in this list are not created
+    create_tables = load_tables + (
+        'form_defs', 
+        'report_params',
+        'syndrome_demog_fields',
+        'tasks',
+        'workqueue_members',
+        'workqueues',
+    )
+
+    def setUp(self):
+        from casemgr.schema import schema
+        from cocklebur import form_ui
+        from casemgr import globals
+        schema.define_db(test_dsn)
+        # Delete any tables we don't intend to use - this considerably speeds
+        # up the creation of the schema.
+        for table_desc in self.db.get_tables():
+            if table_desc.name not in self.create_tables:
+                del self.db.table_describers[table_desc.name]
+        self.db.make_database()
+        self.db.commit()
+        for table in self.load_tables:
+            self.load_table(table)
+        self.db.reset_sequences()
+        self.formlib = globals.formlib =\
+            form_ui.FormLibXMLDB(self.db, 'form_defs')
+        table = self.make_form_table('sars_exposure')
+        self.load_table(table, 'sars_exposure')
+        table = self.make_form_table('hospital_admit')
+        self.load_table(table, 'hospital_admit')
+        self.db.commit()
+
+    def make_form_table(self, formname):
+        from cocklebur.form_ui.xmlload import xmlload
+        from casemgr.formutils import deploy
+        f = open(os.path.join(self.data_path, '%s.form' % formname))
+        try:
+            form = xmlload(f)
+        finally:
+            f.close()
+        self.formlib.save(form, formname)
+        deploy.make_form_table(self.db, form, form.table)
+        return form.table
diff --git a/tests/tuplestruct.py b/tests/tuplestruct.py
new file mode 100644
index 0000000..3185b21
--- /dev/null
+++ b/tests/tuplestruct.py
@@ -0,0 +1,51 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+import cPickle
+from cocklebur.tuplestruct import TupleStruct
+
+class SS(TupleStruct): __slots__ = 'a', 'b'
+
+class Case(unittest.TestCase):
+    def _test(self, ss):
+        self.assertEqual((ss.a, ss.b), (3, 4))
+        self.assertEqual(tuple(ss), (3, 4))
+        self.assertEqual((ss[0], ss[1]), (3, 4))
+        self.assertEqual(len(ss), 2)
+        self.assertRaises(AttributeError, getattr, ss, 'x')
+
+    def runTest(self):
+        self.assertRaises(TypeError, SS, 1)
+        self.assertRaises(TypeError, SS, 1, 2, 3)
+        ss = SS(3, 4)
+        self._test(ss)
+
+        self.assertRaises(TypeError, SS, a=1)
+        self.assertRaises(TypeError, SS, a=1, b=2, c=3)
+        ss = SS(b=4, a=3)
+        self._test(ss)
+
+        ss = cPickle.loads(cPickle.dumps(ss))
+        self._test(ss)
+
+def suite():
+    return Case()
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tests/wikiformatting.py b/tests/wikiformatting.py
new file mode 100644
index 0000000..96deb05
--- /dev/null
+++ b/tests/wikiformatting.py
@@ -0,0 +1,228 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import unittest
+from albatross import SimpleAppContext
+import config
+
+from wiki.formatter import wiki_to_html, wiki_to_oneliner
+from wiki.env import Environment
+from wiki.href import Href
+
+import testcommon
+
+"""
+    _post_rules = [
+        r"(?P<htmlescape>[&<>])",
+#       # shref corresponds to short TracLinks, i.e. sns:stgt
+#       r"(?P<shref>!?((?P<sns>%s):(?P<stgt>%s|%s(?:%s*%s)?)))" \
+#       % (LINK_SCHEME, QUOTED_STRING,
+#          SHREF_TARGET_FIRST, SHREF_TARGET_MIDDLE, SHREF_TARGET_LAST),
+#       # lhref corresponds to long TracLinks, i.e. [lns:ltgt label?]
+#       r"(?P<lhref>!?\[(?:(?P<lns>%s):(?P<ltgt>%s|[^\]\s]*)|(?P<rel>%s))"
+#       r"(?:\s+(?P<label>%s|[^\]]+))?\])" \
+#       % (LINK_SCHEME, QUOTED_STRING, LHREF_RELATIVE_TARGET, QUOTED_STRING),
+#       # macro call
+#       (r"(?P<macro>!?\[\[(?P<macroname>[\w/+-]+)"
+#        r"(\]\]|\((?P<macroargs>.*?)\)\]\]))"),
+        # heading, list, definition, indent, table...
+        r"(?P<heading>^\s*(?P<hdepth>=+)\s.*\s(?P=hdepth)\s*$)",
+        r"(?P<list>^(?P<ldepth>\s+)(?:\*|\d+\.) )",
+        r"(?P<definition>^\s+(.+)::)\s*",
+        r"(?P<indent>^(?P<idepth>\s+)(?=\S))",
+        r"(?P<last_table_cell>\|\|\s*$)",
+        r"(?P<table_cell>\|\|)"]
+"""
+
+LARGE_TEST = """= Pandemic Influenza =
+== Important information ==
+
+ * Something
+ * Whatever
+   * ~~Struck-out~~
+ * Something ```monospaced``` (should be `monospaced`) or {{{**monospaced**}}}
+   * *Bold*
+   * _Italic_
+      * **Bold/italic**
+  * __underlining__ the *importance* of ^upper^ and ,,lower,, bounds for H,,2,,O consumption or 2^3^ = 8
+
+Line 1[[BR]]Line 2
+
+  1. One
+    1. One point one
+  1. Two
+
+ llama::
+  a hairy, spitting beast
+ python::
+  a hairless herpetic beasty
+
+A paragraph of words
+  And an indented paragraph of words which go on and on and on endlessly like some form of verbal diarrhoea or worse, please stop I can't stand it!
+
+A paragraph of words
+  And an indented paragraph of words which go on and on and 
+on endlessly like some form of verbal diarrhoea or worse, 
+please stop I can't stand it!
+
+A paragraph of words
+  And an indented paragraph of words which go on and on and 
+  on endlessly like some form of verbal diarrhoea or worse, 
+  please stop I can't stand it!
+
+||Cell 1||Cell 2||Cell 3||
+||Cell 4||Cell 5||Cell 6||
+
+----
+
+What about URLs?
+
+http://www.cdc.gov 
+
+[http://www.cdc.gov]
+
+[wiki:WonderLand]
+
+WonderLand
+
+http://www.edgewall.com/gfx/trac_example_image.png
+
+[[Timestamp]]
+"""
+
+
+CASEMGR_TESTS = [
+        ('plaintext',
+            '<p>\nplaintext\n</p>\n',
+            'plaintext'),
+        # 'pre' formatting rules
+        ('**hello**',
+            '<p>\n<strong><i>hello</i></strong>\n</p>\n',
+            '<strong><i>hello</i></strong>'),
+        ('*hello*',
+            '<p>\n<strong>hello</strong>\n</p>\n',
+            '<strong>hello</strong>'),
+        ('__hello__',
+            '<p>\n<span class="underline">hello</span>\n</p>\n',
+            '<span class="underline">hello</span>'),
+        ('_hello_',
+            '<p>\n<i>hello</i>\n</p>\n',
+            '<i>hello</i>'),
+        ('~~hello~~',
+            '<p>\n<del>hello</del>\n</p>\n',
+            '<del>hello</del>'),
+        ('^hello^',
+            '<p>\n<sup>hello</sup>\n</p>\n',
+            '<sup>hello</sup>'),
+        (',,hello,,',
+            '<p>\n<sub>hello</sub>\n</p>\n',
+            '<sub>hello</sub>'),
+        ('{{{hello}}}',
+            '<p>\n<tt>hello</tt>\n</p>\n',
+            '<tt>hello</tt>'),
+        ('`hello`',
+            '<p>\n<tt>hello</tt>\n</p>\n',
+            '<tt>hello</tt>'),
+        # 'post' formatting rules
+        ('< > &',
+            '<p>\n< > &\n</p>\n',
+            '< > &'),
+        ('= heading 1 =',
+            '<h1 id="heading1">heading 1</h1>\n',
+            '= heading 1 ='),
+        ('== heading 2 ==',
+            '<h2 id="heading2">heading 2</h2>\n',
+            '== heading 2 =='),
+        ('=== heading 3 ===',
+            '<h3 id="heading3">heading 3</h3>\n',
+            '=== heading 3 ==='),
+        ('==== heading 4 ====',
+            '<h4 id="heading4">heading 4</h4>\n',
+            '==== heading 4 ===='),
+        ('===== heading 5 =====',
+            '<h5 id="heading5">heading 5</h5>\n',
+            '===== heading 5 ====='),
+        ('====== heading 6 ======',
+            '<h5 id="heading6">heading 6</h5>\n', # no level 6
+            '====== heading 6 ======'),
+        (' * hello\n * there',
+            '<ul><li>hello\n</li><li>there\n</li></ul>',
+            '<strong> hello\n </strong> there'),
+        (' * list\n   * sublist',
+            '<ul><li>list\n<ul><li>sublist\n</li></ul></li></ul>',
+            '<strong> list\n   </strong> sublist'),
+        ('  term:: definition',
+            '<dl><dt>term</dt><dd>definition\n</dd></dl>\n',
+            'term:: definition'),
+        (' blockquote',
+            '<blockquote>\n<p>\nblockquote\n</p>\n</blockquote>\n',
+            'blockquote'),
+        ('  blockquote',
+            '<blockquote>\n<p>\nblockquote\n</p>\n</blockquote>\n',
+            'blockquote'),
+        ('||heading 1||heading 2||\n||cell 1||cell 2||',
+            '<table class="wiki">\n<tr><td>heading 1</td><td>heading 2\n</td></tr><tr><td>cell 1</td><td>cell 2\n</td></tr></table>\n',
+            '||heading 1||heading 2||\n||cell 1||cell 2||'),
+        ('{{{\n#!html\n<h1 style="text-align: right; color: blue">HTML Test</h1>\n}}}\n',
+            '<div class="system-message">\n <strong>Error: Failed to load processor <code>html</code></strong>\n <pre>No macro named [[html]] found</pre>\n</div>\n',
+            ' […]'),
+        # a problematic test from Tim
+        (LARGE_TEST,
+            '<h1 id="PandemicInfluenza">Pandemic Influenza</h1>\n<h2 id="Importantinformation">Important information</h2>\n<ul><li>Something\n</li><li>Whatever\n<ul><li><del>Struck-out</del>\n</li></ul></li><li>Something <tt></tt><tt>monospaced</tt><tt></tt> (should be <tt>monospaced</tt>) or <tt>**monospaced**</tt>\n<ul><li><strong>Bold</strong>\n</li><li><i>Italic</i>\n<ul><li><strong><i>Bold/italic</i></strong>\n</li></ul></li></ul></li><li><span class="underline">underlining</span> t [...]
+            '= Pandemic Influenza =\n== Important information ==\n\n <strong> Something\n </strong> Whatever\n   <strong> <del>Struck-out</del>\n </strong> Something <tt></tt><tt>monospaced</tt><tt></tt> (should be <tt>monospaced</tt>) or <tt>**monospaced**</tt>\n   <strong> </strong>Bold<strong>\n   </strong> <i>Italic</i>\n      <strong> </strong><i>Bold/italic</i><strong>\n  </strong> <span class="underline">underlining</span> the <strong>importance</strong> of <sup>upper</sup> and <s [...]
+    ]
+
+# These should remain unchanged
+WIKI_TESTS = [
+		'Tickets: #1 or ticket:1',
+		'Reports: {1} or report:1',
+		'Changesets: r1, [1] or changeset:1',
+		'Revision Logs: r1:3, [1:3] or log:branches/0.8-stable#1:3',
+		'Wiki pages: CamelCase or wiki:CamelCase',
+		'Milestones: milestone:1.0 or milestone:"End-of-days Release"',
+		'Files: source:trunk/COPYING',
+		'Attachments: attachment:"file name.doc"',
+		'A specific file revision: source:/trunk/COPYING#200',
+		'A filename with embedded space: source:"/trunk/README FIRST"',
+    ]
+
+class WikiFmt(testcommon.TestCase):
+    def _test(self, fn, args, expect):
+        result = fn(*args)
+        self.assertEqLines(result, expect, 'input %s' % args[0])
+#        self.assertEqual(result, expect, 
+#                         'input %s, expected %r, got %r' % \
+#                            (args[0], expect, result))
+
+    def setUp(self):
+        self.app = SimpleAppContext(None)
+        self.app.config = config
+        self.env = Environment(self.app)
+        self.env.href = Href(self.env.config.appname)
+
+    def test_casemgr_wiki(self):
+        for input, html_output, oneliner_output in CASEMGR_TESTS:
+            self._test(wiki_to_html, (input, self.env), html_output);
+            self._test(wiki_to_oneliner, (input, self.env), oneliner_output);
+
+    def test_trac_disabled(self):
+        # These Trac markup strings should come through unchanged
+        # (the functionality has been removed)
+        for input in WIKI_TESTS:
+            self._test(wiki_to_html, (input, self.env), 
+                       '<p>\n%s\n</p>\n' % input);
+            self._test(wiki_to_oneliner, (input, self.env), input);
diff --git a/tests/xmlparse.py b/tests/xmlparse.py
new file mode 100644
index 0000000..5bd5452
--- /dev/null
+++ b/tests/xmlparse.py
@@ -0,0 +1,123 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import unittest
+from cStringIO import StringIO
+
+from cocklebur import xmlparse
+
+class Parser(xmlparse.XMLParse):
+    root_tag = 'top'
+
+    class top(xmlparse.ContainerNode):
+        subtags = ('next', 'attr')
+        permit_attrs = ('x', 'y')
+
+    class next(xmlparse.Node):
+        pass
+
+    class attr(xmlparse.Node):
+        permit_attrs = ('one', 'two:int', 'three:bool', 'four:float', 
+                        'five:str', 'six:unicode')
+
+
+class XMLParseTests(unittest.TestCase):
+    def _test(self, xml):
+        return Parser().parse(StringIO(xml))
+
+    def _exc(self, xml):
+        self.assertRaises(xmlparse.ParseError, self._test, xml)
+
+    def test_basic(self):
+        self._test('<top />')
+        self._test('<?xml version="1.0" encoding="UTF-8"?><top />')
+        self._test('<top></top>')
+        self._test('<top>cdata</top>')
+        self._test('\n<top></top>')
+        self._test('<top><next /></top>')
+        self._test('<top><next></next></top>')
+
+    def test_tree(self):
+        top = self._test('<top x="1" y=\'2\'>A\nB<next>\nC\n</next>\nD\n</top>')
+        self.assertEqual(top.name, 'top')
+        self.assertEqual(top.get_text(), 'A\nB\nD\n')
+        self.assertEqual(top.attrs, dict(x='1', y='2'))
+        self.assertEqual(len(top.children), 1)
+        next = top.children[0]
+        self.assertEqual(next.name, 'next')
+        self.assertEqual(next.attrs, {})
+        self.assertEqual(next.get_text(), '\nC\n')
+
+    def test_types(self):
+        top = self._exc('<top><attr two="x" /></top>')
+        top = self._exc('<top><attr two="." /></top>')
+        top = self._exc('<top><attr two="" /></top>')
+        top = self._exc('<top><attr three="1" /></top>')
+        top = self._exc('<top><attr three="bah" /></top>')
+        top = self._exc('<top><attr four="x" /></top>')
+        top = self._exc('<top><attr four="." /></top>')
+        top = self._exc('<top><attr four="" /></top>')
+        top = self._test('<top><attr one="xx" two="2" three="yes" four="4.4" '
+                         'five="yy" six="zz" /><attr two="1000000000000" '
+                         'three="no" four="-1e20" /></top>')
+        self.assertEqual(top.name, 'top')
+        self.assertEqual(top.get_text(), '')
+        self.assertEqual(top.attrs, {})
+        self.assertEqual(len(top.children), 2)
+        attr = top.children[0]
+        self.assertEqual(attr.name, 'attr')
+        self.assertEqual(attr.get_text(), '')
+        self.assertEqual(attr.attrs, 
+            dict(one="xx", two=2, three=True, four=4.4, five="yy", six=u"zz"))
+        attr = top.children[1]
+        self.assertEqual(attr.name, 'attr')
+        self.assertEqual(attr.get_text(), '')
+        self.assertEqual(attr.attrs, 
+            dict(two=1000000000000L, three=False, four=-1e20))
+
+    def test_errors(self):
+        self._exc('')                           # No top level
+        self._exc('</top>')                     # No top level
+        self._exc('<?xml version="1.0" encoding="UTF-8"?>')
+        self._exc('<top>')                      # Not closed
+        self._exc('<top z="1" />')              # Unknown attribute
+        self._exc('<top></pot>')                # Not closed
+        self._exc('cdata<top></top>')
+        self._exc('<top></top>cdata')
+        self._exc('<top></top></top>')          # closed twice
+        self._exc('<top/></top>')               # closed twice
+        self._exc('<next />')                   # Not valid top-level
+        self._exc('<unknown />')                # Unknown tag
+        self._exc('<top><next></top>')          # <next> not closed
+        self._exc('<top><top /></top>')         # <top> not allowed in <top>
+        self._exc('<top/><top />')              # multiple top levels
+        
+
+class suite(unittest.TestSuite):
+    test_list = (
+        'test_basic',
+        'test_tree',
+        'test_types',
+        'test_errors',
+    )
+    def __init__(self):
+        unittest.TestSuite.__init__(self, map(XMLParseTests, self.test_list))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
+
diff --git a/tools/client_report.py b/tools/client_report.py
new file mode 100644
index 0000000..5491a97
--- /dev/null
+++ b/tools/client_report.py
@@ -0,0 +1,431 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+A tool to extract and process "client report" data
+
+client reports look like:
+
+    client report: inst=collection page=tasks unit=1 user=1 start=1197333660 ip=127.0.0.1 server=0.155 client=0.87
+
+"""
+
+import sys
+import os
+import re
+import math
+import time
+import datetime
+import optparse
+import colorsys
+
+class ParseError(Exception): pass
+
+groupby_cols = [
+    'inst', 'page', 'unit', 'user', 'ip', 'forwarded', 
+]
+
+def record_filter(col, regexp):
+    try:
+        regexp = re.compile(regexp)
+    except re.error, e:
+        raise ParseError(e)
+    return lambda record: regexp.search(record.get(col, ''))
+
+def parse_line(line):
+    cr = 'client report: '
+    n = line.find(cr)
+    if n < 0:
+        return                  # not found
+    record = {}
+    for field in line[n+len(cr):-1].split():
+        try:
+            name, value = field.split('=', 1)
+        except ValueError:
+            raise ParseError('could not parse %r' % line)
+        record[name] = value
+    return record
+
+
+fieldre = re.compile(r' (\w+)=')
+def parse_line_forgiving(line):
+    """
+    A slower version of the line parser that copes with spaces within
+    field values.
+    """
+    cr = 'client report:'
+    s = line.find(cr)
+    if s < 0:
+        return                          # not found
+    s += len(cr)
+    e = line.find(', referer: ')        # Apache makes this harder
+    record = {}
+    fields = fieldre.split(line[s:e])
+    n = 1
+    while n < len(fields):
+        record[fields[n]] = fields[n+1]
+        n += 2
+    return record
+
+
+def parse_files(files, start=None, end=None, filter=None,
+                       parser=parse_line_forgiving):
+    for fn in files:
+        try:
+            f = open(fn)
+        except IOError, (eno, estr):
+            sys.exit('%s: %s' % (fn, estr))
+        for line in f:
+            try:
+                record = parser(line)
+            except ParseError, e:
+                sys.exit('%s: %s' % (fn, e))
+            if record is not None:
+                try:
+                    record['t'] = t = int(record['start'])
+                except ValueError:
+                    continue
+                if filter and not filter(record):
+                    continue
+                if ((not start or t >= start) and
+                    (not end or t < end)):
+                    yield record
+        f.close()
+
+
+def write_csv(options, lines):
+    """
+    Write records as CSV
+    """
+    import csv
+
+    fields = [
+        'inst', 'page', 'unit', 'user', 'start', 'ip', 'forwarded', 
+        'server', 'client'
+    ]
+    writer = csv.writer(sys.stdout)
+    writer.writerow(fields)
+    for record in lines:
+        try:
+            t = time.localtime(record['t'])
+            record['start'] = time.strftime('%Y-%m-%d %H:%M:%S', t)
+        except (ValueError, TypeError):
+            pass
+        writer.writerow([record.get(field, '') for field in fields])
+
+
+def write_ds(options, line):
+    """
+    Write records as a NEA dataset
+    """
+    from SOOMv0 import soom, makedataset
+    from SOOMv0.Sources.common import DataSourceBase
+    from mx import DateTime
+
+    class DataSource(DataSourceBase):
+        def __init__(self, lines):
+            DataSourceBase.__init__(self, 'client_report', [])
+            self.lines = lines
+
+        def next_rowdict(self):
+            record = self.lines.next()
+            t = record['t']
+            record['date'] = DateTime.DateFromTicks(t)
+            record['datetime'] = DateTime.DateTimeFromTicks(t)
+            return record
+
+    soom.messages = options.verbose
+    soom.setpath(options.dspath)
+    soom.writepath = soom.searchpath[0]
+
+    ds = makedataset(options.dsname, label=options.dslabel)
+    ds.addcolumn('inst', label='Application instance', 
+                          coltype='categorical', datatype='recode')
+    ds.addcolumn('page', label='Application page', 
+                          coltype='categorical', datatype='recode')
+    ds.addcolumn('user', label='User ID', 
+                          coltype='scalar', datatype='int')
+    ds.addcolumn('unit', label='Unit ID', 
+                          coltype='scalar', datatype='int')
+    ds.addcolumn('date', label='Report date', 
+                          coltype='ordinal', datatype='date')
+    ds.addcolumn('datetime', label='Report date/time', 
+                          coltype='ordinal', datatype='datetime')
+    ds.addcolumn('ip', label='Remote IP address', 
+                          coltype='categorical', datatype='recode')
+    ds.addcolumn('forwarded', label='Forwarded IP address', 
+                          coltype='categorical', datatype='recode')
+    ds.addcolumn('server', label='Server time', 
+                          coltype='scalar', datatype='float')
+    ds.addcolumn('client', label='Client time', 
+                          coltype='scalar', datatype='float')
+    ds.loaddata(DataSource(lines), initialise=True, finalise=True)
+    ds.save()
+
+
+def make_n_colors(n):
+    # We should just return a 3-tuple of float RGB values, but matplotlib is
+    # too smart for it's own good, and if the plot has three x values, it
+    # unsuccessfully tries to interpret this as colour values for each X value,
+    # so we use the HTML #rrggbb format instead
+    return ['#%02x%02x%02x' % colorsys.hsv_to_rgb(float(i) / n, 0.8, 255.0) 
+            for i in range(n)]
+
+
+class PlotGroup(object):
+    __slots__ = ('name', 'x', 'y', 'color', 'handle')
+
+    bins_per_day = 24.0
+
+    def __init__(self, name):
+        self.name = str(name)
+        self.x = []
+        self.y = []
+
+    def to_rate(self):
+        counts = {}
+        for x in self.x:
+            epoch = math.floor(x * self.bins_per_day)
+            counts[epoch] = counts.get(epoch, 0) + 1
+        x = []
+        y = []
+        for epoch, count in counts.iteritems():
+            x.append(epoch / self.bins_per_day)
+            y.append(count)
+        self.x, self.y = x, y
+
+
+def avg(seq):
+    total = 0.0
+    count = 0
+    for v in seq:
+        total += v
+        count += 1
+    return total / count
+
+
+def plot(options, lines):
+    try:
+        import pylab
+    except ImportError:
+        sys.exit('MatPlotLib (pylab) not found?')
+
+    if options.local:
+        measure = 'server'
+    else:
+        measure = 'client'
+    groupby = options.groupby
+
+    day_seconds = 24.0*60*60
+    gregorian_offs = datetime.datetime.fromtimestamp(0).toordinal()
+
+    data = {}
+    last_g = NotImplemented
+    g = None
+    for record in lines:
+        t = record.get('t')
+        m = record.get(measure)
+        if t and m:
+            if groupby:
+                g = record.get(groupby)
+            if g != last_g:
+                try:
+                    group = data[g]
+                except KeyError:
+                    group = data[g] = PlotGroup(g)
+                last_g = g
+            t = t / day_seconds + gregorian_offs
+            group.x.append(t)
+            group.y.append(float(m))
+    if options.rate:
+        for group in data.itervalues():
+            group.to_rate()
+    if not data:
+        sys.exit('No data found')
+    for color, group in zip(make_n_colors(len(data)), data.values()):
+        group.color = color
+
+    if 'TZ' in os.environ:
+        pylab.rcParams['timezone'] = os.environ['TZ']
+    elif os.path.exists('/etc/timezone'):
+        tzname = open('/etc/timezone').next().strip()
+        try:
+            pylab.rcParams['timezone'] = tzname        
+        except Exception:
+            pass
+
+    fig = pylab.figure(figsize=(10,6), dpi=120, facecolor='w')
+    data = data.values()
+    
+    # Render the least common points on top...
+    data.sort(lambda a, b: cmp(len(b.x), len(a.x)))
+    for group in data:
+        group.handle = pylab.plot_date(group.x, group.y, color=group.color, 
+                                       marker='.')
+    if options.groupby:
+        if len(data) > 12:
+            data.sort(lambda a, b: cmp(avg(b.y), avg(a.y)))
+            data = data[:12]
+        else:
+            data.sort(lambda a, b: cmp(a.name, b.name))
+        handles = [g.handle for g in data]
+        labels = ['%4.1f - %s' % (avg(g.y), g.name) for g in data]
+        legend = pylab.legend(handles, labels, numpoints=1, loc='upper left')
+        pylab.setp(legend.get_texts(), fontsize=8)
+    ax = pylab.gca()
+    #ax.grid(True)
+    ax.set_axis_bgcolor('#cccccc')
+    if options.logscale:
+        ax.set_yscale("log")
+    ax.autoscale_view()
+    fig.autofmt_xdate()
+    if options.rate:
+        pylab.ylabel('Requests per hour')
+        title = 'Request rate'
+    elif options.local:
+        pylab.ylabel('Server response (in seconds)')
+        title = 'Server response'
+    else:
+        pylab.ylabel('Round trip (in seconds)')
+        title = 'Round-trip'
+    if options.groupby:
+        title += ' by %s' % options.groupby
+    if options.filter:
+        title += ' where %s' % options.filter
+    pylab.title(title)
+
+    #pylab.xlabel('Date/time')
+
+    if options.plotfile:
+        pylab.savefig(options.plotfile)
+    else:
+        pylab.show()
+
+
+def parse_datetime_opt(date):
+    try:
+        t = time.strptime(date, '%Y-%m-%d %H:%M:%S')
+    except ValueError:
+        try:
+            t = time.strptime(date, '%Y-%m-%d %H:%M')
+        except ValueError:
+            try:
+                t = time.strptime(date, '%Y-%m-%d')
+            except ValueError:
+                raise ParseError('Cannot parse date %r (use ISO YYYY-MM-DD '
+                                 'format)' % date)
+    return time.mktime(t)
+
+
+if __name__ == '__main__':
+    optp = optparse.OptionParser(usage='usage: %prog [options] <logfile> ...')
+    optp.add_option('-c', '--csv', 
+                    action='store_true', default=False,
+                    help='emit CSV records')
+
+    optg = optparse.OptionGroup(optp, 'Filtering')
+    optg.add_option('--start', 
+                    help='Ignore records before START')
+    optg.add_option('--end', 
+                    help='Ignore records after END')
+    optg.add_option('--filter', metavar='COLUMN=REGEXP',
+                    help='Only produce rows where COLUMN matches REGEXP')
+    optp.add_option_group(optg)
+
+    optg = optparse.OptionGroup(optp, 'NetEpi Analysis dataset options'
+                    '(specify dataset name for ds output mode)')
+    optg.add_option('-N', '--dsname', 
+                    help='NetEpi Analysis dataset name')
+    optg.add_option('--dslabel', default='NetEpi Collection client reports',
+                    help='NetEpi Analysis dataset label')
+    optg.add_option('--dspath', 
+                    help='NetEpi Analysis dataset path')
+    optg.add_option('-v', '--verbose', 
+                    action='store_true', default=False,
+                    help='Verbose operation')
+    optp.add_option_group(optg)
+
+    optg = optparse.OptionGroup(optp, 'MatPlotLib options')
+    optg.add_option('-p', '--plot', 
+                    action='store_true', default=False,
+                    help='Graph response times (requires MatPlotLib)')
+    optg.add_option('-g', '--groupby', metavar='COLUMN',
+                    help='Group-by column (one of: %s)' % 
+                            ', '.join(groupby_cols))
+    optg.add_option('-l', '--local', 
+                    action='store_true', default=False,
+                    help='Plot local (server) times, rather than client '
+                         'round-trip times (default)')
+    optg.add_option('-r', '--rate', 
+                    action='store_true', default=False,
+                    help='Plot request rate')
+    optg.add_option('--logscale', 
+                    action='store_true', default=False,
+                    help='Use log scale for y axis')
+    optg.add_option('--plotfile', '--output',
+                    help='write plot to FILE', metavar='FILE')
+    optp.add_option_group(optg)
+
+    options, args = optp.parse_args()
+
+    if options.start:
+        try:
+            options.start = parse_datetime_opt(options.start)
+        except ParseError, e:
+            optp.error('--start: %s' % e)
+    if options.end:
+        try:
+            options.end = parse_datetime_opt(options.end)
+        except ParseError, e:
+            optp.error('--start: %s' % e)
+    filter_fn = None
+    if options.filter:
+        try:
+            col, regexp = options.filter.split('=')
+        except ValueError:
+            optp.error('filter syntax is COLUMN=REGEXP')
+        if col not in groupby_cols:
+            optp.error('--filter column must be one of %s' % 
+                            ', '.join(groupby_cols))
+        try:
+            filter_fn = record_filter(col, regexp)
+        except ParseError, e:
+            optp.error(e)
+    if options.groupby and options.groupby not in groupby_cols:
+        optp.error('--groupby must be one of %s' % ', '.join(groupby_cols))
+
+    if not args:
+        optp.error('specify at least one input file')
+
+    oneof = 'csv', 'dsname', 'plot'
+    if sum([bool(getattr(options, name)) for name in oneof]) != 1:
+        optp.error('specify one and only one of %s' %
+            ', '.join(['--' + name for name in oneof]))
+
+    lines = parse_files(args, start=options.start, end=options.end,
+                              filter=filter_fn)
+
+    if options.csv:
+        write_csv(options, lines)
+
+    if options.dsname:
+        write_ds(options, lines)
+
+    if options.plot:
+        plot(options, lines)
diff --git a/tools/client_report1.png b/tools/client_report1.png
new file mode 100644
index 0000000..871d32f
Binary files /dev/null and b/tools/client_report1.png differ
diff --git a/tools/client_report2.png b/tools/client_report2.png
new file mode 100644
index 0000000..4fc9941
Binary files /dev/null and b/tools/client_report2.png differ
diff --git a/tools/client_report3.png b/tools/client_report3.png
new file mode 100644
index 0000000..0f1beca
Binary files /dev/null and b/tools/client_report3.png differ
diff --git a/tools/client_report4.png b/tools/client_report4.png
new file mode 100644
index 0000000..eff1992
Binary files /dev/null and b/tools/client_report4.png differ
diff --git a/tools/client_report5.png b/tools/client_report5.png
new file mode 100644
index 0000000..3f4428f
Binary files /dev/null and b/tools/client_report5.png differ
diff --git a/tools/client_report6.png b/tools/client_report6.png
new file mode 100644
index 0000000..3a80e33
Binary files /dev/null and b/tools/client_report6.png differ
diff --git a/tools/client_report_page.py b/tools/client_report_page.py
new file mode 100644
index 0000000..be22c12
--- /dev/null
+++ b/tools/client_report_page.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Generate a page with various plots from the client reports
+"""
+
+import sys
+import os
+
+import client_report
+
+class Options:
+    def __init__(self, d):
+        self.filter = None
+        self.groupby = None
+        self.local = False
+        self.logscale = False
+        self.outfile = None
+        self.rate = False
+        self.__dict__.update(d)
+
+def generate_page(outdir, *infiles):
+    def plot(lines, name, **kwargs):
+        fn  = name + '.png'
+        outfiles.append(fn)
+        kwargs['plotfile'] = os.path.join(outdir, fn)
+        client_report.plot(Options(kwargs), lines)
+    outfiles = []
+    lines = list(client_report.parse_files(infiles))
+    plot(lines, 'rate', rate=True)
+    plot(lines, 'local_page', local=True, groupby='page')
+    plot(lines, 'remote_page', local=False, groupby='page')
+    plot(lines, 'remote_ip', local=False, groupby='ip')
+    f = open(os.path.join(outdir, 'index.html'), 'w')
+    f.write('<html><body><center>')
+    for of in outfiles:
+        f.write('<img src="%s"><br>' % of)
+    f.write('</center></body></html>')
+    f.close()
+
+
+if __name__ == '__main__':
+    os.environ['TZ'] = 'Australia/NSW'
+    if len(sys.argv) < 3:
+        sys.exit('Usage: %s <outdir> <infiles> ...')
+    generate_page(*sys.argv[1:])
+
diff --git a/tools/compile_db.py b/tools/compile_db.py
new file mode 100644
index 0000000..5b12673
--- /dev/null
+++ b/tools/compile_db.py
@@ -0,0 +1,139 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# HISTORICAL NOTE:
+#
+# This script is now somewhat of a misnomer: initially form definitions were
+# python code, and we compiled these into python bytecode (.pyc files). Over
+# time, the script gained the ability to create the database schema, and
+# sometime after that, forms moved to an XML format stored in the database, so
+# there's no compiling going on anymore, just the database creation, schema
+# upgrades and some consistency checks.
+
+import sys, os, imp, traceback, re
+try:
+    set
+except NameError:
+    from sets import Set as set
+
+verbose = False
+debug = False
+
+def get_ugid(user):
+    import pwd
+    pw = pwd.getpwnam(user)
+    return pw.pw_uid, pw.pw_gid
+
+def make_dirs(dir, owner):
+    if not os.path.exists(dir):
+        par_dir = os.path.dirname(dir)
+        make_dirs(par_dir, owner)
+        os.mkdir(dir, 0755)
+        os.chown(dir, *owner)
+
+
+def compile_db(dsn, target_dir, db_user):
+    from casemgr.schema import schema, upgrade, check, seed
+
+    dbobj.execute_debug(debug)
+    db_desc_dir = os.path.join(target_dir, 'db')
+    db = schema.define_db(dsn)
+    print 'Phase 1 - schema upgrades'
+    upgrade.Upgrades(db, target_dir).run()
+    db.commit()
+    print 'Phase 2 - create new entities'
+    db.make_database() # implicit commit
+    print 'Phase 3 - form checks'
+    check.check_form_dependancies(db, db_user)
+    db.commit()
+    print 'Phase 4 - fix table ownership'
+    db.chown(db_user)
+    print 'Phase 5 - seeding database'
+    seed.seed_db(db)
+    db.commit()
+    print 'Phase 6 - write describer'
+    owner = get_ugid(db_user)
+    make_dirs(db_desc_dir, owner)
+    db.save_describer(db_desc_dir, owner = owner, mode = 0644)
+    return db
+
+def usage():
+    sys.exit('''\
+Usage: %s [opts] <dsn> <cgi_target_dir>
+    -u <user>   grant db tables and chown files to this user
+    -G          grant only (don't preen database)
+    -R          revoke only (don't preen database)
+    -v          verbose
+    -D          debug''' % sys.argv[0])
+    
+
+if __name__ == '__main__':
+    import getopt
+    
+    user = 'www-data'
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], '?hu:vDTGR')
+    except getopt.GetoptError, e:
+        sys.exit(e)
+    grant_only = revoke_only = False
+    for opt, arg in opts:
+        if opt in ('-h', '-?'):
+            usage()
+        elif opt == '-u':
+            user = arg
+        elif opt == '-v':
+            verbose = True
+        elif opt == '-D':
+            debug = True
+        elif opt == '-G':
+            grant_only = True
+        elif opt == '-R':
+            revoke_only = True
+    try:
+        dsn, cgi_target = args
+    except ValueError:
+        usage()
+    if grant_only and revoke_only:
+        usage()
+    sys.path.insert(0, cgi_target)
+    from cocklebur import dbobj
+    if grant_only or revoke_only:
+        dbobj.execute_debug(debug)
+        db = dbobj.get_db(os.path.join(cgi_target, 'db'), dsn)
+        curs = db.cursor()
+        for table_desc in db.get_tables():
+            if grant_only:
+                table_desc.grant(curs, user)
+            if revoke_only:
+                table_desc.revoke(curs, user)
+        curs.close()
+        db.commit()
+        db.close()
+    else:
+        db = compile_db(dsn, cgi_target, user)
+elif __name__ == '__install__':
+    print "checking database"
+    sys.path.insert(0, config.cgi_target)
+    from cocklebur import dbobj
+
+    verbose = config.install_verbose
+    debug = config.install_debug
+    target = config.cgi_target
+    if config.install_prefix:
+        target = os.path.join(config.install_prefix, target)
+    db = compile_db(config.dsn, target, config.web_user)
diff --git a/tools/csvfilter.py b/tools/csvfilter.py
new file mode 100644
index 0000000..1dc4aa4
--- /dev/null
+++ b/tools/csvfilter.py
@@ -0,0 +1,149 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import os
+import sys
+import csv
+import optparse
+
+class Error(Exception): pass
+
+class Matcher:
+
+    def __init__(self, header, field, value):
+        self.field = field
+        self.value = value
+        try:
+            self.index = header.index(self.field)
+        except ValueError:
+            raise Error('column %r not found' % self.field)
+
+
+class Include(Matcher):
+
+    def match(self, include, row):
+        if not include:
+            include = row[self.index] == self.value
+        return include
+        
+
+class Exclude(Matcher):
+
+    def match(self, include, row):
+        if include:
+            include = row[self.index] != self.value
+        return include
+
+
+def make_matchers(rules, header):
+    matchers = []
+    for op, field, value in rules:
+        if op == '=':
+            matchers.append(Include(header, field, value))
+        else:
+            matchers.append(Exclude(header, field, value))
+    return matchers
+
+
+def yield_rows(matchers, in_rows):
+    initial = isinstance(matchers[0], Exclude)
+    for row in in_rows:
+        include = initial
+        for matcher in matchers:
+            include = matcher.match(include, row)
+        if include:
+            yield row
+
+
+def write_rows(header, filter_rows, out_fn, limit=None):
+    out_f = open(out_fn, 'wb')
+    try:
+        writer = csv.writer(out_f)
+        writer.writerow(header)
+        out_count = 0
+        for row in filter_rows:
+            writer.writerow(row)
+            out_count += 1
+            if limit and out_count >= limit:
+                break
+    except Exception:
+        try:
+            out_f.close()
+        except OSError:
+            pass
+        os.unlink(out_fn)
+        raise
+    else:
+        out_f.close()
+        return limit and out_count >= limit
+
+
+def filter(options, rules, in_fn, out_fn):
+    f = open(in_fn, 'rb')
+    try:
+        reader = csv.reader(f)
+        header = reader.next()
+        try:
+            matchers = make_matchers(rules, header)
+        except Error, e:
+            raise Error('%s: %s' % (in_fn, e))
+        filter_rows = yield_rows(matchers, reader)
+        if not options.chunk:
+            write_rows(header, filter_rows, out_fn)
+        else:
+            n = 1
+            base, ext = os.path.splitext(out_fn)
+            while write_rows(header, filter_rows, 
+                             '%s_%d%s' % (base, n, ext),
+                             limit=options.chunk):
+                n += 1
+    finally:
+        f.close()
+
+
+usage = 'Usage: %prog [options] column=include column!=exclude in_file out_file'
+
+def main():
+    optp = optparse.OptionParser(usage=usage)
+    optp.add_option('-c', '--chunk', type='int',
+                    help='Split output in files of CHUNK rows or less')
+    options, args = optp.parse_args()
+    rules = []
+    files = []
+    for arg in args:
+        try:
+            field, value = arg.split('!=')
+        except ValueError:
+            try:
+                field, value = arg.split('=')
+            except ValueError:
+                files.append(arg)
+            else:
+                rules.append(('=', field, value))
+        else:
+            rules.append(('!', field, value))
+    if len(files) != 2 or not rules:
+        optp.print_help()
+        sys.exit(1)
+    try:
+        filter(options, rules, *files)
+    except Error, e:
+        sys.exit(e)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/csvsplit.py b/tools/csvsplit.py
new file mode 100644
index 0000000..5ae211f
--- /dev/null
+++ b/tools/csvsplit.py
@@ -0,0 +1,98 @@
+#! /usr/bin/env python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Split a file into parts preserving header line(s) in a manner akin
+to split(1).
+"""
+
+import sys, optparse, os
+
+
+ALPHABET = 'abcdefghijklmnopqrstuvwxyz'
+
+
+def int2str(i, length):
+    digits = []
+    while length > 0 or i:
+        digits.insert(0, ALPHABET[i % 26])
+        i /= 26
+        length -= 1
+    return ''.join(digits)
+    
+
+
+def csvsplit(args):
+    parser = optparse.OptionParser(usage="usage: %prog [options] [file [name]]")
+    parser.add_option('-?', action='help', help=optparse.SUPPRESS_HELP)
+    parser.add_option('-a', '--suffix-length',
+            type='int', default=2,
+            metavar='m',
+            help='use m letters to form the suffix of the file name [default: %default]')
+    parser.add_option('-H', '--header-lines',
+            type='int', default=1,
+            metavar='h',
+            help='repeat h lines from the start of the input file at the start of each output file [default: %default]')
+    parser.add_option('-l', '--line-count',
+            type='int', default=1000,
+            metavar='n',
+            help='create smaller files n lines in length [default: %default]')
+    options, args = parser.parse_args(args)
+    if len(args) > 2:
+        parser.error('too many arguments')
+    if not args or args[0] == '-':
+        input = sys.stdin
+    else:
+        input = file(args[0], 'r')
+    if options.suffix_length <= 0:
+        parser.error('suffix length must be positive')
+    if options.header_lines < 0:
+        parser.error('number of header lines must be non-negative')
+    if options.line_count <= 0:
+        parser.error('line count must be positive')
+    if len(args) == 2:
+        prefix = args[1]
+    else:
+        prefix = 'x.csv'
+    prefix, ext = os.path.splitext(prefix)
+    part = 0
+    count = 0
+    # grab the lines to repeat
+    if options.header_lines > 0:
+        header = []
+        for l in input:
+            header.append(l)
+            if len(header) == options.header_lines:
+                header = ''.join(header)
+                break
+    else:
+        header = ''
+    # loop over input
+    for l in input:
+        # start a new output file as necessary
+        if count % options.line_count == 0:
+            output = file(prefix + int2str(part, options.suffix_length) + ext, 'w')
+            output.write(header)
+            part += 1
+        output.write(l)
+        count += 1
+    output.close()
+
+if __name__ == '__main__':
+    csvsplit(sys.argv[1:])
diff --git a/tools/desc_csv.py b/tools/desc_csv.py
new file mode 100644
index 0000000..d4200c1
--- /dev/null
+++ b/tools/desc_csv.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Produce a simple description of the data in a CSV file, attempting to detect
+types and reporting maximum field width.
+"""
+import sys
+import os
+import csv
+import re
+
+isodate_re = re.compile(r'^\d{4}-\d{2}-\d{2}$')
+isodatetime_re = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')
+decimal_re = re.compile(r'^\d*\.\d*$')
+
+class Col:
+    def __init__(self, name):
+        self.name = name
+        self.maxlen = 0
+        self.maybe_isodate = True
+        self.maybe_isodatetime = True
+        self.maybe_decimal = True
+        self.maybe_int = True
+
+    def add(self, value):
+        value_len = len(value)
+        if value_len > self.maxlen:
+            self.maxlen = value_len
+        if self.maybe_decimal and not decimal_re.match(value):
+            self.maybe_decimal = False
+        if self.maybe_isodate and not isodate_re.match(value):
+            self.maybe_isodate = False
+        if self.maybe_isodatetime and not isodatetime_re.match(value):
+            self.maybe_isodatetime = False
+        if self.maybe_int and not value.isdigit():
+            self.maybe_int = False
+        
+    def desc(self):
+        if self.maybe_int:
+            type = 'int'
+        elif self.maybe_decimal:
+            type = 'decimal'
+        elif self.maybe_isodate:
+            type = 'date'
+        elif self.maybe_isodatetime:
+            type = 'datetime'
+        else:
+            type = 'string'
+        return self.name, type, self.maxlen
+
+
+def desc_file(fn):
+    cols = None
+    for row in csv.reader(open(fn, 'rb')):
+        if cols is None:
+            cols = [Col(name) for name in row]
+        else:
+            for col, value in zip(cols, row):
+                if value:
+                    col.add(value)
+    f = open('%s.desc' % fn, 'w')
+    try:
+        w = csv.writer(f)
+        w.writerow(('name', 'type', 'len'))
+        for col in cols:
+            w.writerow(col.desc())
+    finally:
+        f.close()
+
+if __name__ == '__main__':
+    for fn in sys.argv[1:]:
+        desc_file(fn)
diff --git a/tools/dupescangraph.py b/tools/dupescangraph.py
new file mode 100644
index 0000000..ef132f7
--- /dev/null
+++ b/tools/dupescangraph.py
@@ -0,0 +1,43 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import re
+import pylab
+
+rep=re.compile(r'matching took (\d+)\..* (\d+) records')
+
+def plot(fns):
+    x = []
+    y = []
+    for fn in fns:
+        for line in open(fn):
+            match = rep.search(line)
+            if match:
+                x.append(int(match.group(2)))
+                y.append(float(match.group(1)) / 60)
+    pylab.plot(x, y)
+    a = pylab.gca()
+    a.set_xlabel('Persons')
+    a.set_ylabel('Minutes')
+    pylab.title('Dupe scan time')
+    pylab.savefig('dupescan.png')
+                
+
+if __name__ == '__main__':
+    plot(sys.argv[1:])
diff --git a/tools/extract_demogfields.py b/tools/extract_demogfields.py
new file mode 100644
index 0000000..6a04bca
--- /dev/null
+++ b/tools/extract_demogfields.py
@@ -0,0 +1,48 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import os
+import csv
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+from casemgr.notification.client import dummy_notification_client
+
+class DummyGlobals:
+    notify = dummy_notification_client()
+
+import casemgr
+sys.modules['casemgr.globals'] = casemgr.globals = DummyGlobals()
+
+from casemgr import demogfields
+
+
+writer = csv.writer(sys.stdout)
+attrs = (
+    'name','label','render','section','entity',
+    'show_case','show_form','show_person','show_result',
+    'field_case','field_form','field_person','field_result',
+)
+
+writer.writerow(attrs)
+for field in demogfields.demog_classes:
+    row = []
+    for attr in attrs:
+        row.append(getattr(field, attr, None))
+    writer.writerow(row)
diff --git a/tools/faxtag.py b/tools/faxtag.py
new file mode 100644
index 0000000..2df0c5b
--- /dev/null
+++ b/tools/faxtag.py
@@ -0,0 +1,121 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import re
+import ocpgdb as dbapi 
+
+syndrome_id = 8
+
+splitre = re.compile(r'[ ,/]*')
+
+def yield_cases(db):
+    curs = db.cursor()
+    curs.execute('select case_id, fax_phone from cases join persons using (person_id) where syndrome_id=%s and fax_phone is not null', [syndrome_id])
+    for case_id, tags in curs.fetchall():
+        yield case_id, tags
+
+def tally(it):
+    tally = {}
+    for case_id, tags in it:
+        for tag in splitre.split(tags):
+            tag = tag.upper()
+            try:
+                ts = tally[tag]
+            except KeyError:
+                ts = tally[tag] = set()
+            ts.add(case_id)
+    lt = [(len(ts), tag) for tag, ts in tally.iteritems()]
+    lt.sort()
+    lt.reverse()
+    for cnt, tag in lt[:40]:
+        print '%5d %s' % (cnt, tag)
+
+
+match_tags = [
+    'DOH LAB TEST ENTRY', 'COMPLETE EMR',
+    'RN1', 'PD1', 'RE1', 'CP1', 
+    'GP NO ACTION', 'COMM', 'RF:UPDATED', 'GPSS', 'GP SURVEILLANCE',
+    'SCHOOL', 'SCHOOLCONTACT', 'TONGANCONTACT', 'TONGAN', 'KAPOOKA',
+    'INPATIENT', 'OUTPATIENT', 'DISCHARGED', 'ICU',
+    'ACTCONTACT', 'ACT', 'ACTX', 'VIC', 'VICCONTACT', 'QLD', 'QLDCONTACT',
+    'C ICU DISCHARGE DATA UPDATED',
+    'OS', 'CONTACTED', 'CRICKET', 'ELINK', 'NARR1',
+    'CONCORD OUTPATIENT',
+    'FLU CLINIC',
+]
+
+tag_remap = {
+    'GP SURVEILLANCE': 'GPSS',
+}
+
+def preproc(it):
+    tagspat = '|'.join([r'\s*\b%s\b\s*' % tag for tag in match_tags])
+    tagsre = re.compile('(%s)' % tagspat, re.I)
+    ignored = {}
+    cases_tags = []
+    seen_tags = set()
+    for case_id, tags in it:
+        case_tags = set()
+        for i, t in enumerate(tagsre.split(tags)):
+            t = t.strip().upper()
+            if i & 1:
+                t = tag_remap.get(t, t)
+                t = t.replace(' ', '_')
+                case_tags.add(t)
+                seen_tags.add(t)
+            elif t:
+                try:
+                    cs = ignored[t]
+                except KeyError:
+                    cs = ignored[t] = set()
+                cs.add(case_id)
+        cases_tags.append((case_id, case_tags))
+    return seen_tags, cases_tags
+
+
+def make_tags(db, seen_tags):
+    curs = db.cursor()
+    tag_ids = {}
+    for tag in seen_tags:
+        curs.execute("SELECT nextval('tags_tag_id_seq')")
+        id = curs.fetchone()[0]
+        curs.execute('INSERT INTO tags (tag_id, tag) VALUES (%s, %s)',
+                        (id, tag))
+        tag_ids[tag] = id
+    return tag_ids
+
+
+def tag_cases(db, tag_ids, cases_tags):
+    curs = db.cursor()
+    for case_id, tags in cases_tags:
+        for tag in tags:
+            curs.execute('INSERT INTO case_tags (tag_id, case_id) VALUES (%s,%s)', (tag_ids[tag], case_id))
+
+
+def main(dbname):
+    db = dbapi.connect(database=dbname)
+    #tally(yield_cases(db))
+    seen_tags, cases_tags = preproc(yield_cases(db))
+
+    tag_ids = make_tags(db, seen_tags)
+    tag_cases(db, tag_ids, cases_tags)
+    db.commit()
+
+
+main('sftest')
+
diff --git a/tools/form_to_xml.py b/tools/form_to_xml.py
new file mode 100644
index 0000000..7e9e3f7
--- /dev/null
+++ b/tools/form_to_xml.py
@@ -0,0 +1,37 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+import os
+from cocklebur.form_ui.pyload import pyload
+from cocklebur.form_ui.xmlsave import xmlsave
+
+def form_to_xml(filename):
+    f = open(filename)
+    try:
+        form = pyload(f)
+    finally:
+        f.close()
+    form.update_columns()
+    form.name = os.path.basename(filename)[:-3]
+    xmlsave(sys.stdout, form)
+
+if __name__ == '__main__':
+    if len(sys.argv) != 2 or not sys.argv[1].endswith('.py'):
+        sys.exit('Usage: %s <form.py>' % sys.argv[0])
+    form_to_xml(sys.argv[1])
diff --git a/tools/formexport.py b/tools/formexport.py
new file mode 100644
index 0000000..53a25f5
--- /dev/null
+++ b/tools/formexport.py
@@ -0,0 +1,204 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys, os
+import re
+try:
+    set
+except NameError:
+    from sets import Set as set
+from optparse import OptionParser, OptionGroup
+
+
+def formlist(forms, names, options):
+    if options.historical:
+        formlist = forms
+    else:
+        formlist = forms.latest()
+    if names:
+        names = set(names)
+        for flf in formlist:
+            if flf.name in names:
+                yield flf
+    else:
+        for flf in formlist:
+            yield flf
+
+def list_forms(forms, names, options):
+    for flf in formlist(forms, names, options):
+        print '%s:%d' % (flf.name, flf.version)
+
+
+def export_forms(forms, names, options):
+    if options.format == 'py':
+        from cocklebur.form_ui.pysave import pysave as writer
+    else:
+        from cocklebur.form_ui.xmlsave import xmlsave as writer
+    for flf in formlist(forms, names, options):
+        form = flf.load()
+        if options.historical:
+            fn = '%s_%d.form' % (flf.name, flf.version)
+        else:
+            fn = '%s.form' % flf.name
+        fn = os.path.join(options.exportdir, fn)
+        try:
+            f = open(fn, 'w')
+        except IOError, (eno, estr):
+            print >> sys.stderr, '%s: %s' % (fn, estr)
+            continue
+        bytecount = None
+        try:
+            try:
+                writer(f, form)
+                bytecount = f.tell()
+            finally:
+                f.close()
+        except Exception:
+            os.unlink(fn)
+            raise
+        if options.verbose and bytecount is not None:
+            print 'exported %s:%s as %s (%d bytes)' %\
+                (flf.name, flf.version, fn, bytecount)
+
+class FFError(Exception): pass
+
+class FF:
+    form_mod_re = re.compile('^([a-zA-Z0-9_]+?)(_[0-9]+)?(\.[^.]*)$')
+
+    def __init__(self, name, fn):
+        if not os.path.exists(fn):
+            raise FFError('%s: does not exist' % fn)
+        self.fn = fn
+        match = self.form_mod_re.match(name)
+        if not match or match.group(3) not in ('.form', '.py'):
+            raise FFError('%s: ignored' % fn)
+        self.name = match.group(1)
+        self.version = match.group(2)
+        if self.version is not None:
+            self.version = int(self.version[1:])
+
+    def __cmp__(self, other):
+        return cmp((self.name, self.version), (other.name, other.version))
+
+def update_forms(db, name, form):
+    query = db.query('forms')
+    query.where('label = %s', name)
+    if not query.fetchone():
+        row = db.new_row('forms')
+        row.label = name
+        row.name = form.text
+        row.allow_multiple = form.allow_multiple
+        row.form_type = form.form_type
+        row.def_update_time = form.update_time
+        row.db_update()
+
+def import_forms(db, forms, names, options):
+    if options.format == 'py':
+        from cocklebur.form_ui.pyload import pyload as reader
+    else:
+        from cocklebur.form_ui.xmlload import xmlload as reader
+    form_mod_re = re.compile('^([a-zA-Z0-9_]+?)(_[0-9]+)?(\.[^.]*)$')
+    if names:
+        names = set(names)
+    name_versions = {}
+    for name in os.listdir(options.importdir):
+        fn = os.path.join(options.importdir, name)
+        try:
+            ff = FF(name, fn)
+        except FFError, e:
+            print >> sys.stderr, e
+            continue
+        if not names or ff.name in names or name in names:
+            name_versions.setdefault(ff.name, []).append(ff)
+    for versions in name_versions.itervalues():
+        versions.sort()
+        if not options.historical:
+            del versions[:-1]
+        for ff in versions:
+            f = open(ff.fn)
+            try:
+                try:
+                    form = reader(f)
+                finally:
+                    f.close()
+            except Exception, e:
+                print >> sys.stderr, '%s: %s' % (ff.fn, e)
+                continue
+            version = forms.save(form, ff.name)
+            update_forms(db, ff.name, form)
+            if options.verbose:
+                print 'imported %s as %s:%s' % (ff.fn, ff.name, version)
+    db.commit()
+
+
+def main():
+    parser = OptionParser(usage='usage: %prog [options] <formname> ...')
+    parser.add_option('-H', '--historical', 
+                      dest='historical', action='store_true',
+                      help='include historical versions')
+    parser.add_option('-v', '--verbose', 
+                      dest='verbose', action='store_true',
+                      help='print status messages to stdout')
+    parser.add_option('-D', '--debug', 
+                      dest='debug', action='store_true',
+                      help='enable debugging output')
+    parser.add_option('-C', '--cgi', metavar='DIR',
+                      dest='cgidir', default='/usr/www/cgi-bin/collection',
+                      help='Collection cgi-bin directory (default %default)')
+    parser.add_option('-F', '--format', 
+                      dest='format', default='xml',
+                      help='form format (xml or py, default %default)')
+    group = OptionGroup(parser, 'Actions')
+    group.add_option('-i', '--import', 
+                      dest='importdir', metavar='DIR',
+                      help='import forms from DIR')
+    group.add_option('-o', '--export', 
+                      dest='exportdir', metavar='DIR',
+                      help='export forms to DIR')
+    group.add_option('-l', '--list', 
+                      dest='list', action='store_true', 
+                      help='list forms in library')
+    parser.add_option_group(group)
+    options, args = parser.parse_args()
+    modeopts = options.importdir, options.exportdir, options.list
+    modecnt = sum([bool(o) for o in modeopts])
+    if not modecnt:
+        parser.error('Specify one of --import, --export or --list.')
+    elif modecnt > 1:
+        parser.error('--import, --export and --list are mutually exclusive.')
+    if options.format not in ('xml', 'py'):
+        parser.error('--format must be xml or py.')
+    sys.path.insert(0, options.cgidir)
+    try:
+        import config as config
+        from cocklebur import dbobj
+        from cocklebur.form_ui import formlib
+    except ImportError:
+        parser.error('import casemgr modules failed, check --cgi argument')
+    dbobj.execute_debug(options.debug)
+    db = dbobj.get_db(os.path.join(options.cgidir, 'db'), config.dsn)
+    forms = formlib.FormLibXMLDB(db, 'form_defs')
+    if options.list:
+        list_forms(forms, args, options)
+    elif options.exportdir:
+        export_forms(forms, args, options)
+    elif options.importdir:
+        import_forms(db, forms, args, options)
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/hdfilter.py b/tools/hdfilter.py
new file mode 100644
index 0000000..da93b24
--- /dev/null
+++ b/tools/hdfilter.py
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+A script to pre-process Health Declaration csv data, normalising
+some fields, and optionally filtering on flight and/or seat (with
+row proximity). Also:
+ - e-mail fields are concatenated as sectiona_email and sectionb_email
+ - address fields are concatenated as sectiona1, sectiona2, sectionb1, sectionb2
+"""
+
+import sys
+import os
+import csv
+import re
+import optparse
+
+merge_fields = (
+    'name',
+    'address',
+    'state',
+    'postcode',
+    'phone',
+    'mobilephone',
+    'email',
+)
+
+class ParseError(Exception): pass
+
+def normalise(v):
+    if v:
+        return v.strip().upper()
+
+
+normalise_flight = normalise
+
+row_re = re.compile(r'^\s*[A-Z]?([0-9]+)[A-Z]?\s*$', re.IGNORECASE)
+
+def parse_seat_row(seat):
+    if not seat:
+        return
+    match = row_re.match(seat)
+    if not match:
+        raise ParseError('Could not parse seat: %r' % seat)
+    return int(match.group(1))
+
+
+def seat_near(seat):
+    seat_row = parse_seat_row(seat)
+    return (seat_row >= options.row - options.proximity
+            and seat_row <= options.row + options.proximity)
+
+
+def filter_row(row):
+    row['flight_number'] = normalise_flight(row['flight_number'])
+    if options.flight and options.flight != row['flight_number']:
+        return False
+    if options.seat:
+        if (not seat_near(row['seat_number']) and 
+            (not row['alternative_seat_number'] or
+            not seat_near(row['alternative_seat_number']))):
+            return False
+    return True
+
+
+def combine(row, *fields):
+    values = []
+    seen = set()
+    for field in fields:
+        value = row[field]
+        if value:
+            value = value.strip()
+        if value:
+            lvalue = value.lower()
+            if lvalue not in seen:
+                seen.add(lvalue)
+                values.append(value)
+    return ', '.join(values)
+
+
+new_fields = [
+    'sectiona_email',
+    'sectiona_phone',
+    'sectiona_mobilephone',
+    'sectionb',
+]
+def process_row(row):
+    row['sectiona_email'] = combine(row, 'sectiona_email1', 'sectiona_email2')
+    row['sectiona_phone'] = combine(row, 'sectiona_phone1', 'sectiona_phone2')
+    row['sectiona_mobilephone'] = combine(row, 'sectiona_mobilephone1', 
+                                               'sectiona_mobilephone2')
+    sec = 'b'
+    blocks = []
+    seen = set()
+    for subsec in '12':
+        lines = []
+        for field in merge_fields:
+            value = row.get('section%s_%s%s' % (sec, field, subsec))
+            if value and value.strip():
+                lines.append('%s: %s' % (field, value.strip()))
+        lines = '\r\n'.join(lines)
+        if lines and lines not in seen:
+            seen.add(lines)
+            blocks.append(lines)
+    row['section%s' % sec] = '\r\n\r\n'.join(blocks)
+    return row
+
+
+def process_file(out_f, fn):
+    header = None
+    f = open(fn, 'rb')
+    in_count = out_count = err_count = 0
+    try:
+        reader = csv.reader(f)
+        writer = csv.writer(out_f)
+        for row in reader:
+            if header is None:
+                header = [col.lower() for col in row]
+                header.extend(new_fields)
+                writer.writerow(header)
+            else:
+                in_count += 1
+                row = dict(map(None, header, row))
+                try:
+                    if not filter_row(row):
+                        continue
+                except ParseError, m:
+                    err_count += 1
+                    sys.stderr.write('WARNING: %s, row %d: %s\n' %
+                                     (fn, in_count + 1, m))
+                try:
+                    row = process_row(row)
+                except ParseError, m:
+                    err_count += 1
+                    sys.stderr.write('ERROR: %s, row %d: %s\n' %
+                                     (fn, in_count + 1, m))
+                else:
+                    if row:
+                        writer.writerow([row[col] for col in header])
+                        out_count += 1
+    finally:
+        f.close()
+    if options.verbose:
+        print 'Read %d records from %s, wrote %s records, %d warnings' %\
+            (in_count, fn, out_count, err_count)
+
+
+def main():
+    global options
+
+    from optparse import OptionParser
+
+    optp = OptionParser()
+    optp.add_option('--flight', dest="flight",
+                    help="Filter on FLIGHT")
+    optp.add_option('--seat', dest="seat",
+                    help="Filter on SEAT")
+    optp.add_option('--proximity', dest="proximity", default=2, type='int',
+                    help="Row proximity for seat filter (default: 2)")
+    optp.add_option('-o', dest="outfile",
+                    help="Write output to FILE", metavar='FILE')
+    optp.add_option('-v', dest="verbose", action='store_true', default=False,
+                    help="Verbose output")
+
+    options, args = optp.parse_args()
+    if len(args) != 1:
+        optp.error('No input file specified')
+
+    options.flight = normalise_flight(options.flight)
+    try:
+        options.row = parse_seat_row(options.seat)
+    except ParseError, m:
+        optp.error(m)
+    if options.outfile:
+        out_f = open(options.outfile, 'wb')
+    else:
+        out_f = sys.stdout
+    try:
+        process_file(out_f, *args)
+    except Exception, e:
+        if options.outfile:
+            out_f.close()
+            os.unlink(options.outfile)
+        raise
+    else:
+        if options.outfile:
+            out_f.close()
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/hrigcalc.html b/tools/hrigcalc.html
new file mode 100644
index 0000000..4bee593
--- /dev/null
+++ b/tools/hrigcalc.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+
+    The contents of this file are subject to the HACOS License Version 1.2
+    (the "License"); you may not use this file except in compliance with
+    the License.  Software distributed under the License is distributed
+    on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+    implied. See the LICENSE file for the specific language governing
+    rights and limitations under the License.  The Original Software
+    is "NetEpi Collection". The Initial Developer of the Original
+    Software is the Health Administration Corporation, incorporated in
+    the State of New South Wales, Australia.
+    
+    Copyright (C) 2004-2011 Health Administration Corporation, Australian
+    Government Department of Health and Ageing, and others.
+    All Rights Reserved.
+
+    Contributors: See the CONTRIBUTORS file for details of contributions.
+
+-->
+<html>
+ <head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <script type="text/javascript">
+   form_name = 'appform';
+   field_name = 'form_data.weight_kgs';
+   iu_dose_per_kg = 20;
+   titre = 150;
+   vial_ml = 2;
+   rationing = true;
+
+   function get_src_field () {
+    var src_form = window.opener.document.forms[form_name];
+    if (!src_form) return;
+    return src_form[field_name];
+   }
+
+   function getFloat(fieldName) {
+    var field = document.getElementById(fieldName);
+    return parseFloat(field.value === undefined ? field.innerHTML : field.value);
+   }
+   function display(elementName, state) {
+    var node = document.getElementById(elementName);
+    node.style.display = state ? '' : 'none';
+   }
+
+   function setFloat(fieldName, value, prec) {
+    var field = document.getElementById(fieldName);
+    if (!field) throw Error('Unknown field ' + fieldName);
+    if (field.value === undefined)
+     field.innerHTML = value.toFixed(prec);
+    else
+     field.value = value.toFixed(prec);
+   }
+
+   function init () {
+    setFloat('iu_dose_per_kg', iu_dose_per_kg);
+    setFloat('titre', titre);
+    setFloat('vial_ml', vial_ml);
+    if (window.opener) {
+     var field = document.getElementById('weight');
+     var src_field = get_src_field();
+     if (!src_field) return;
+     field.value = src_field.value;
+     update();
+    }
+    document.forms[0][0].focus();
+   }
+
+   function update () {
+    var error_field = document.getElementById('error');
+    var result_div = document.getElementById('result');
+    if (!error_field || !result_div) return;
+    result_div.style.display = 'none';
+    error_field.innerHTML = '';
+    try {
+     var weight = getFloat('weight');
+     if (weight < 0) {
+      error_field.innerHTML = 'Weight must be > 0';
+      return;
+     }
+     if (weight > 600 || isNaN(weight)) {
+      error_field.innerHTML = 'Bad weight';
+      return;
+     }
+     setFloat('weight_1', weight, 1);
+     var dose_iu = weight * iu_dose_per_kg;
+     setFloat('dose_iu_1', dose_iu);
+     setFloat('dose_iu_2', dose_iu);
+     var dose_ml = dose_iu / titre;
+     setFloat('dose_ml_1', dose_ml, 1);
+     setFloat('dose_ml_2', dose_ml, 1);
+     var vials = dose_ml / vial_ml;
+     setFloat('vials', vials, 1);
+     var fraction = vials - Math.floor(vials);
+     var rounded_vials;
+     if (rationing && weight >= 60 && fraction < 0.25) {
+      display('rationing_on', true)
+      display('rationing_off', false)
+      rounded_vials = Math.floor(vials);
+     } else {
+      display('rationing_on', false)
+      display('rationing_off', true)
+      rounded_vials = Math.ceil(vials);
+     }
+     setFloat('rounded_vials', rounded_vials);
+     result_div.style.display = 'block';
+    } finally {
+     if (result_div.style.display != 'block' && !error_field.innerHTML)
+      error_field.innerHTML = 'An internal error has occurred';
+    }
+   }
+  </script>
+  <style>
+   body {
+    background-color: white;
+    font-size: 10pt;
+    font-family: "Verdana", "Arial", sans-serif;
+   }
+   .info {
+    width: 80%;
+    color: #444;
+    border-bottom: 1px solid #ccc;
+    border-top: 1px solid #ccc;
+    margin: 1em 2em;
+   }
+   .error {
+    color: red;
+    font-weight: bold;
+   }
+   .result {
+    display: none;
+    color: #262;
+    font-size: 120%;
+    margin: 1em 0;
+   }
+  </style>
+  <title>NSWHealth HRIG calculator</title>
+ </head>
+ <body onload="init();">
+  <h1>NSWHealth HRIG calculator</h1>
+  <h3>Calculating the amount of HRIG required:</h3>
+  <div class="info">
+   The Australian Immunisation Handbook recommends 20 IU of HRIG per
+   kg of weight for all ages. Each ml of HRIG has a minimum titre of
+   150 IU. Each vial contains 2 ml of HRIG. When HRIG supplies are at
+   critical levels, savings of HRIG should be made by rationing. For
+   adults weighing over 60 kg (who will be receiving a minimum of 1200
+   IU in 8 ml) an extra vial should not be used where 0.5 ml or less is
+   indicated to be injected.
+  </div>
+
+  <form onsubmit="try {update()} finally {return false}">
+   <label for="weight">Weight (in kg):</label>
+   <input id="weight" name="weight" onchange="update();" />
+   <input type="submit" value="Okay" />
+   <div class="error" id="error"></div>
+   <div class="result" id="result">
+    <div>
+     <b><span id="weight_1"></span> kg</b> x <span id="iu_dose_per_kg"></span>
+     IU per kg of HRIG = <span id="dose_iu_1"></span> IU of HRIG
+    </div>
+    <div>
+     <span id="dose_iu_2"></span> IU / <span id="titre"></span> IU per ml = 
+     <span id="dose_ml_1"></span> ml of HRIG
+    </div>
+    <div>
+     <span id="dose_ml_2"></span> ml of HRIG / <span id="vial_ml"></span>
+     ml per vial = <span id="vials"></span> fractional vial(s)
+    </div>
+    <div>
+     <span id="rationing_on">Rationing rules apply, </span>
+     <span id="rationing_off">Rationing rules do not apply, </span>
+     order <b><span id="rounded_vials"></span> vial(s)</b>.
+    </div>
+   </div>
+   <input type="button" onclick="window.close()" value="Close">
+  </form>
+ </body>
+</html>
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/INSTALL b/tools/mod_auth_pgsql-2.0.3-netepi.4101/INSTALL
new file mode 100644
index 0000000..d7b7dc9
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/INSTALL
@@ -0,0 +1,48 @@
+
+From mod_auth_pgsql version 2.0,  apache >= 2.0.40 and PostgreSQL >= 7.1 are needed
+	for apache 1.3.x use the mod_auth_pgsql 0.9.x tree
+
+
+APXS DSO Install:
+	- untar mod_auth_pgsql  
+		cd /usr/local/src
+		tar zxf mod_auth_pgsql-2.0.x.tar.gz
+		cd mod_auth_pgsql-2.0.x
+	- make & install as DSO
+	 	
+	 	/usr/local/apache2/bin/apxs  -i -a -c -I /usr/local/pgsql/include -L /usr/local/pgsql/lib -lpq mod_auth_pgsql.c
+ 
+	
+
+STATIC Install
+
+
+	- untar mod_auth_pgsql  
+		cd /usr/local/src
+		tar zxf mod_auth_pgsql-2.0.x.tar.gz
+
+	- untar apache source and run ./configure from the apache source dir
+		cd /usr/local/src
+		tar zxf httpd-2.0.x.tar.gz
+		
+		cd httpd-2.0.x
+		./configure --with-module=aaa:../mod_auth_pgsql-2.0.x/mod_auth_pgsql.c
+
+	- edit config_vars.mk
+		vi build/config_vars.mk		
+		add "-I/usr/local/pgsql/include" to the end of  EXTRA_INCLUDES 
+		add "-lpq -L/usr/local/pgsql/lib" to the end of  EXTRA_LIBS
+		
+		NOTE: they have to be the last in the line, otherwise we get conflicts between pgsql & apache includes
+		NOTE2: there is a way to do it in a better way (without vi ) ? 
+		   
+	- make & install	   
+		make
+		make install
+		
+
+
+
+bye,
+  Giuseppe Tanzilli <info at giuseppetanzilli.it>
+
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/Makefile b/tools/mod_auth_pgsql-2.0.3-netepi.4101/Makefile
new file mode 100644
index 0000000..7f47114
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/Makefile
@@ -0,0 +1,12 @@
+APACHE2_HOME=/usr
+PGSQL_LIB=/usr/lib64/pgsql
+PGSQL_INCLUDE=/usr/include/pgsql
+
+shared:
+	${APACHE2_HOME}/sbin/apxs  -i -a -c -I ${PGSQL_INCLUDE} -L ${PGSQL_LIB} -lpq mod_auth_pgsql.c
+
+indent:
+	indent -kr -ts4 mod_auth_pgsql.c
+
+clean:
+	rm -rf .libs/ *.la *.o *.lo *.slo *~	
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/README b/tools/mod_auth_pgsql-2.0.3-netepi.4101/README
new file mode 100644
index 0000000..81862eb
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/README
@@ -0,0 +1,22 @@
+
+For Install instructions read INSTALL file
+
+For Configuration information see the html documentation
+
+Changelog, see html file mod_auth_pgsql.html
+
+
+Please report bugs with platform and apache/postgresql release information
+
+HomePage http://www.giuseppetanzilli.it/mod_auth_pgsql2/
+
+Old module for apache 1.3 HomePage http://www.giuseppetanzilli.it/mod_auth_pgsql/
+
+
+              Maintainer:
+              Giuseppe Tanzilli
+                      info at giuseppetanzilli.it
+                      g.tanzilli at gruppocsf.com
+              Original source:
+	    	          Adam Sussman <asussman at vidya.com> Feb, 1996
+    	    	      Matthias Eckermann <eckerman at lrz.uni-muenchen.de>
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/README.netepi b/tools/mod_auth_pgsql-2.0.3-netepi.4101/README.netepi
new file mode 100644
index 0000000..0c69210
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/README.netepi
@@ -0,0 +1,62 @@
+Using NetEpi Collection accounts for authentication under Apache
+================================================================
+
+The supplied mod_auth_pgsql contains modifications to support
+NetEpi's password hashing mechanism to allow NetEpi Collection
+accounts to be used to authenticate access under Apache.
+
+Once compiled and installed under Apache (see instructions within
+mod_auth_pgsql for details) the following configuration options
+become available:
+
+    Auth_PG_hash_type NETEPI
+    Auth_PG_netepi_old_passwords [on|off]
+
+A new 'NETEPI' value may be specified to Auth_PG_hash_type to
+supplement the existing password hashing mechanism.
+
+The Auth_PG_netepi_old_passwords option is provided to allow NetEpi
+Collection password created with version of NetEpi Collection earlier
+than 1.5 to work.  This option is off by default and must be
+explicitly enabled.
+
+NOTE: In addition to these options, additional (existing) mod_auth_pgsql
+options must be supplied to allow correct use of the NetEpi Collection
+database schema.  See the example.
+
+Example
+-------
+
+The following example .htaccess file, when placed in a directory
+and Apache is correctly configured, allows units 5, 11 and 12 access.
+
+    AuthName "NetEpi protected area"
+    AuthType Basic
+
+    # conventional db options
+    Auth_PG_host the_db_host
+    Auth_PG_user the_db_user
+    Auth_PG_pwd "the db password"
+    Auth_PG_database netepi
+    Auth_PG_pwd_table users
+    Auth_PG_pwd_whereclause "AND enabled AND NOT deleted"
+    Auth_PG_uid_field username
+    Auth_PG_pwd_field password
+    Auth_PG_hash_type NETEPI
+    #Auth_PG_netepi_old_passwords off
+    
+    # the following allow mod_auth_pgsql to construct correctly formed queries against the db
+    Auth_PG_grp_table "units G JOIN unit_users GU USING (unit_id) JOIN users U USING (user_id)"
+    Auth_PG_grp_whereclause "AND G.enabled AND U.enabled AND NOT U.deleted"
+    Auth_PG_grp_user_field "U.username"
+    Auth_PG_grp_group_field "G.unit_id"
+
+    # and finally the unts to be allowed access
+    Require group 5 11 12
+
+Determining unit ids
+--------------------
+
+The following query will show unit names and their associated ids:
+
+    select unit_id, name from units;
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/TODO b/tools/mod_auth_pgsql-2.0.3-netepi.4101/TODO
new file mode 100644
index 0000000..77acc69
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/TODO
@@ -0,0 +1,7 @@
+To be done:
+- test 
+- add a group conf/require group example in the doc
+- investigate why the module will be reconfigured (per directory) at every request
+	but only if we configure by .htaccess instead of Directory in httpd.cof
+- password caching with shared cache (?)
+- new documentation
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.c b/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.c
new file mode 100644
index 0000000..7262fab
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.c
@@ -0,0 +1,1236 @@
+/* =====================================================================
+ * Copyright (c) 1996 Vidya Media Ventures, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of this source code or a derived source code must
+ *    retain the above copyright notice, this list of conditions and the
+ *    following disclaimer. 
+ *
+ * 2. Redistributions of this module or a derived module in binary form
+ *    must reproduce the above copyright notice, this list of conditions
+ *    and the following disclaimer in the documentation and/or other
+ *    materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY VIDYA MEDIA VENTURES, INC. ``AS IS'' AND 
+ * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL VIDYA MEDIA VENTURES, INC.
+ * OR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software is a contribution to and makes use of the Apache HTTP
+ * server which is written, maintained and copywritten by the Apache Group.
+ * See http://www.apache.org/ for more information.
+ *
+ * This software makes use of libpq which an interface to the PostgreSQL
+ * database.  PostgreSQL is copyright (c) 1994 by the Regents of the
+ * University of California.  As of this writing, more information on
+ * PostgreSQL can be found at http://www.postgresSQL.org/
+ *
+ */
+
+/*
+ * 
+ * PostgreSQL authentication
+ *
+ *
+ * Needs libpq-fe.h and libpq.a
+ *
+ * Outline:
+ *
+ * - Authentication
+ *   One database, and one (or two) tables.  One table holds the username and
+ *   the encryped (or plain) password.  The other table holds the username and the names
+ *   of the group to which the user belongs.  It is possible to have username,
+ *   groupname and password in the same table.
+ * - Access Logging
+ *   Every authentication access is logged in the same database of the 
+ *   authentication table, but in different table.
+ *   User name and date of the request are logged.
+ *   As option, it can log password, ip address, request line.
+ *
+ * Module Directives:  See html documentation
+ * 
+ * Changelog: See html documentation
+ *
+ * see http://www.postgreSQL.org/
+ *
+ *
+ *
+ *		Homepage	http://www.giuseppetanzilli.it/mod_auth_pgsql/
+ *
+ *		Latest sources  http://www.giuseppetanzilli.it/mod_auth_pgsql/dist/
+ *
+ *		Maintainer:
+ *		Giuseppe Tanzilli
+ *			info at giuseppetanzilli.it
+ *			g.tanzilli at gruppocsf.com		
+ *
+ *		Original source: 
+ * 		Adam Sussman (asussman at vidya.com) Feb, 1996
+ *		Matthias Eckermann
+ *		eckerman at lrz.uni-muenchen.de
+ *
+ */
+
+
+#define 	AUTH_PGSQL_VERSION		"2.0.3"
+
+#include "apr_hooks.h"
+#include "apr.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_buckets.h"
+#include "apr_md5.h"
+#include "apr_sha1.h"
+#include "apr_network_io.h"
+#include "apr_pools.h"
+#include "apr_uri.h"
+#include "apr_date.h"
+#include "apr_fnmatch.h"
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_main.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_script.h"
+
+#ifdef WIN32
+#define crypt apr_password_validate
+#else
+#include <unistd.h>
+#endif
+#include "libpq-fe.h"
+
+#if (MODULE_MAGIC_NUMBER_MAJOR < 20020628)
+#error  Apache 2.0.40 required
+#endif
+
+/*
+** uncomment the following line if you're having problem. The debug messages
+** will be written to the server error log.
+** WARNING: we log sensitive data, do not use on production systems
+*/
+
+/* #define DEBUG_AUTH_PGSQL 1 */
+
+
+
+#ifndef MAX_STRING_LEN
+#define MAX_STRING_LEN 8192
+#endif
+
+/* Cache table size */
+#ifndef MAX_TABLE_LEN
+#define MAX_TABLE_LEN 50
+#endif
+
+#define AUTH_PG_HASH_TYPE_CRYPT 	1
+#define AUTH_PG_HASH_TYPE_MD5   	2
+#define AUTH_PG_HASH_TYPE_BASE64	3
+#define AUTH_PG_HASH_TYPE_NETEPI	4
+
+
+typedef struct {
+	const char *dir;
+	const char *auth_pg_host;
+	const char *auth_pg_database;
+	const char *auth_pg_port;
+	const char *auth_pg_options;
+	const char *auth_pg_user;
+	const char *auth_pg_pwd;
+	const char *auth_pg_pwd_table;
+	const char *auth_pg_uname_field;
+	const char *auth_pg_pwd_field;
+	const char *auth_pg_grp_table;
+	const char *auth_pg_grp_group_field;
+	const char *auth_pg_grp_user_field;
+	const char *auth_pg_pwd_whereclause;
+	const char *auth_pg_grp_whereclause;
+
+	int auth_pg_nopasswd;
+	int auth_pg_authoritative;
+	int auth_pg_lowercaseuid;
+	int auth_pg_uppercaseuid;
+	int auth_pg_pwdignorecase;
+	int auth_pg_encrypted;
+	int auth_pg_hash_type;
+	int auth_pg_cache_passwords;
+	int auth_pg_netepi_old_passwords;
+
+	const char *auth_pg_log_table;
+	const char *auth_pg_log_addrs_field;
+	const char *auth_pg_log_uname_field;
+	const char *auth_pg_log_pwd_field;
+	const char *auth_pg_log_date_field;
+	const char *auth_pg_log_uri_field;
+
+	apr_table_t *cache_pass_table;
+
+} pg_auth_config_rec;
+
+static apr_pool_t *auth_pgsql_pool = NULL;
+static apr_pool_t *auth_pgsql_pool_base64 = NULL;
+
+static char *netepi_pw_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+module AP_MODULE_DECLARE_DATA auth_pgsql_module;
+
+
+static int pg_log_auth_user(request_rec * r, pg_auth_config_rec * sec, 
+		char *user, char *sent_pw);
+static char *do_pg_query(request_rec * r, char *query, 
+		pg_auth_config_rec * sec);
+
+static void *create_pg_auth_dir_config(apr_pool_t * p, char *d)
+{
+	pg_auth_config_rec *new_rec;
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p,
+				  "[mod_auth_pgsql.c] - going to configure directory \"%s\" ",
+				  d);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	new_rec = apr_pcalloc(p, sizeof(pg_auth_config_rec));
+	if (new_rec == NULL)
+		return NULL;
+
+	if (auth_pgsql_pool == NULL)
+		apr_pool_create_ex(&auth_pgsql_pool, NULL, NULL, NULL);
+	if (auth_pgsql_pool == NULL)
+		return NULL;
+	/* sane defaults */
+	if (d != NULL)
+		new_rec->dir = apr_pstrdup(p, d);
+	else
+		new_rec->dir = NULL;
+	new_rec->auth_pg_host = NULL;
+	new_rec->auth_pg_database = NULL;
+	new_rec->auth_pg_port = NULL;
+	new_rec->auth_pg_options = NULL;
+	new_rec->auth_pg_user = NULL;
+	new_rec->auth_pg_pwd = NULL;
+	new_rec->auth_pg_pwd_table = NULL;
+	new_rec->auth_pg_uname_field = NULL;
+	new_rec->auth_pg_pwd_field = NULL;
+	new_rec->auth_pg_grp_table = NULL;
+	new_rec->auth_pg_grp_user_field = NULL;
+	new_rec->auth_pg_grp_group_field = NULL;
+	new_rec->auth_pg_pwd_whereclause = NULL;
+	new_rec->auth_pg_grp_whereclause = NULL;
+
+	new_rec->auth_pg_nopasswd = 0;
+	new_rec->auth_pg_authoritative = 1;
+	new_rec->auth_pg_lowercaseuid = 0;
+	new_rec->auth_pg_uppercaseuid = 0;
+	new_rec->auth_pg_pwdignorecase = 0;
+	new_rec->auth_pg_encrypted = 1;
+	new_rec->auth_pg_hash_type = AUTH_PG_HASH_TYPE_CRYPT;
+	new_rec->auth_pg_cache_passwords = 0;
+	new_rec->auth_pg_netepi_old_passwords = 0;
+
+	new_rec->auth_pg_log_table = NULL;
+	new_rec->auth_pg_log_addrs_field = NULL;
+	new_rec->auth_pg_log_uname_field = NULL;
+	new_rec->auth_pg_log_pwd_field = NULL;
+	new_rec->auth_pg_log_date_field = NULL;
+	new_rec->auth_pg_log_uri_field = NULL;
+
+	/* make a per directory cache table */
+	new_rec->cache_pass_table =
+		apr_table_make(auth_pgsql_pool, MAX_TABLE_LEN);
+	if (new_rec->cache_pass_table == NULL)
+		return NULL;
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p,
+				  "[mod_auth_pgsql.c] - configured directory \"%s\" ", d);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	return new_rec;
+}
+
+
+static const char *pg_set_hash_type(cmd_parms * cmd, void *mconfig,
+			 const char *hash_type)
+{
+	pg_auth_config_rec *sec = mconfig;
+
+	if (!strcasecmp(hash_type, "MD5"))
+		sec->auth_pg_hash_type = AUTH_PG_HASH_TYPE_MD5;
+	else if (!strcasecmp(hash_type, "CRYPT"))
+		sec->auth_pg_hash_type = AUTH_PG_HASH_TYPE_CRYPT;
+	else if (!strcasecmp(hash_type, "BASE64"))
+		sec->auth_pg_hash_type = AUTH_PG_HASH_TYPE_BASE64;
+	else if (!strcasecmp(hash_type, "NETEPI"))
+		sec->auth_pg_hash_type = AUTH_PG_HASH_TYPE_NETEPI;
+	else
+		return apr_pstrcat(cmd->pool,
+						   "Invalid hash type for Auth_PG_hash_type: ",
+						   hash_type, NULL);
+	return NULL;
+}
+
+static const char *pg_set_authoritative_flag(cmd_parms * cmd,
+		  pg_auth_config_rec * sec, const int arg)
+{
+	sec->auth_pg_authoritative = arg;
+	return NULL;
+}
+
+static const char *pg_set_lowercaseuid_flag(cmd_parms * cmd, void *sec,
+											const int arg)
+{
+	((pg_auth_config_rec *) sec)->auth_pg_lowercaseuid = arg;
+	((pg_auth_config_rec *) sec)->auth_pg_uppercaseuid = 0;
+	return NULL;
+}
+
+static const char *pg_set_uppercaseuid_flag(cmd_parms * cmd, void *sec,
+											int arg)
+{
+	((pg_auth_config_rec *) sec)->auth_pg_uppercaseuid = arg;
+	((pg_auth_config_rec *) sec)->auth_pg_lowercaseuid = 0;
+	return NULL;
+}
+
+
+static const command_rec pg_auth_cmds[] = {
+	AP_INIT_TAKE1("Auth_PG_host", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec, auth_pg_host),
+				  OR_AUTHCFG,
+				  "the host name of the postgreSQL server."),
+	AP_INIT_TAKE1("Auth_PG_database", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_database), OR_AUTHCFG,
+				  "the name of the database that contains authorization information."),
+	AP_INIT_TAKE1("Auth_PG_port", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec, auth_pg_port),
+				  OR_AUTHCFG,
+				  "the port the postmaster is running on."),
+	AP_INIT_TAKE1("Auth_PG_options", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_options), OR_AUTHCFG,
+				  "an options string to be sent to the postgres backed process."),
+	AP_INIT_TAKE1("Auth_PG_user", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec, auth_pg_user),
+				  OR_AUTHCFG,
+				  "user name connect as"),
+	AP_INIT_TAKE1("Auth_PG_pwd", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec, auth_pg_pwd),
+				  OR_AUTHCFG,
+				  "password to connect"),
+	AP_INIT_TAKE1("Auth_PG_pwd_table", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_pwd_table), OR_AUTHCFG,
+				  "the name of the table containing username/password tuples."),
+	AP_INIT_TAKE1("Auth_PG_pwd_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_pwd_field), OR_AUTHCFG,
+				  "the name of the password field."),
+	AP_INIT_TAKE1("Auth_PG_uid_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_uname_field), OR_AUTHCFG,
+				  "the name of the user-id field."),
+	AP_INIT_TAKE1("Auth_PG_grp_table", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_grp_table), OR_AUTHCFG,
+				  "the name of the table containing username/group tuples."),
+	AP_INIT_TAKE1("Auth_PG_grp_group_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_grp_group_field),
+				  OR_AUTHCFG,
+				  "the name of the group-name field."),
+	AP_INIT_TAKE1("Auth_PG_grp_user_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_grp_user_field),
+				  OR_AUTHCFG,
+				  "the name of the group-name field."),
+	AP_INIT_FLAG("Auth_PG_nopasswd", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_nopasswd),
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_encrypted", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_encrypted),
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_TAKE1("Auth_PG_hash_type", pg_set_hash_type, NULL, OR_AUTHCFG,
+				  "password hash type (CRYPT|MD5|BASE64|NETEPI)."),
+	AP_INIT_FLAG("Auth_PG_netepi_old_passwords", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_netepi_old_passwords),
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_cache_passwords", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_cache_passwords),
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_authoritative", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_authoritative), OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_lowercase_uid", pg_set_lowercaseuid_flag, NULL,
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_uppercase_uid", pg_set_uppercaseuid_flag, NULL,
+				 OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_FLAG("Auth_PG_pwd_ignore_case", ap_set_flag_slot,
+				 (void *) APR_OFFSETOF(pg_auth_config_rec,
+									   auth_pg_pwdignorecase), OR_AUTHCFG,
+				 "'on' or 'off'"),
+	AP_INIT_TAKE1("Auth_PG_grp_whereclause", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_grp_whereclause),
+				  OR_AUTHCFG,
+				  "an SQL fragement that can be attached to the end of a where clause."),
+	AP_INIT_TAKE1("Auth_PG_pwd_whereclause", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_pwd_whereclause),
+				  OR_AUTHCFG,
+				  "an SQL fragement that can be attached to the end of a where clause."),
+	AP_INIT_TAKE1("Auth_PG_log_table", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_table),
+				  OR_AUTHCFG,
+				  "the name of the table containing log tuples."),
+	AP_INIT_TAKE1("Auth_PG_log_addrs_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_addrs_field),
+				  OR_AUTHCFG,
+				  "the name of the field containing addrs in the log table (type char)."),
+	AP_INIT_TAKE1("Auth_PG_log_uname_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_uname_field),
+				  OR_AUTHCFG,
+				  "the name of the field containing username in the log table (type char)."),
+	AP_INIT_TAKE1("Auth_PG_log_pwd_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_pwd_field),
+				  OR_AUTHCFG,
+				  "the name of the field containing password in the log table (type char)."),
+	AP_INIT_TAKE1("Auth_PG_log_date_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_date_field),
+				  OR_AUTHCFG,
+				  "the name of the field containing date in the log table (type char)."),
+	AP_INIT_TAKE1("Auth_PG_log_uri_field", ap_set_string_slot,
+				  (void *) APR_OFFSETOF(pg_auth_config_rec,
+										auth_pg_log_uri_field),
+				  OR_AUTHCFG,
+				  "the name of the field containing uri (Object fetched) in the log table (type char)."),
+	{NULL}
+};
+
+
+static char pg_errstr[MAX_STRING_LEN];
+		 /* global errno to be able to handle config/sql 
+		  * failures separately
+		  */
+
+static char *auth_pg_md5(char *pw)
+{
+	apr_md5_ctx_t ctx;
+	unsigned char digest[APR_MD5_DIGESTSIZE];
+	static unsigned char md5hash[APR_MD5_DIGESTSIZE * 2 + 1];
+	int i;
+
+	apr_md5(digest, (const unsigned char *) pw, strlen(pw));
+
+	for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+		apr_snprintf((char *) &md5hash[i + i], 3, "%02x", digest[i]);
+
+	md5hash[APR_MD5_DIGESTSIZE * 2] = '\0';
+	return (char *) md5hash;
+}
+
+
+static char *auth_pg_base64(char *pw)
+{
+	if (auth_pgsql_pool_base64 == NULL)
+		apr_pool_create_ex(&auth_pgsql_pool_base64, NULL, NULL, NULL);
+	if (auth_pgsql_pool == NULL)
+		return NULL;
+
+	return ap_pbase64encode(auth_pgsql_pool, pw);
+}
+
+
+/*
+ * NetEpi password hashing scheme routines
+ * Based on pwcheck.py in NetEpi CaseMgr
+ */
+
+static char* netepi_crypt(request_rec *r, char *password, char *encrypted_password_with_salt)
+{
+	/* We do this ourselves, because platform crypt() implementations vary too
+	 * much, and we'd like our data to be portable.
+	 *
+	 * We need to extract the salt from the encrypted_password_with_salt string
+	 * prefixed by $S$xxxxxxxx$.
+	 * We know it starts with '$' at this point.
+     */
+	char *saltcopy;
+	char *salt;
+	char *saveptr;
+	char *salted_password;
+	char sha1hash[APR_SHA1PW_IDLEN + APR_SHA1_DIGESTSIZE + 1];
+	char *final_hash;
+
+	saltcopy = apr_pstrdup(r->pool, encrypted_password_with_salt);
+	if (saltcopy == NULL)
+		return NULL;
+	salt = apr_strtok(saltcopy, "$", &saveptr);
+	if (salt == NULL || strcmp(salt, "S"))
+		return NULL;	/* Wrong hash method */
+	salt = apr_strtok(NULL, "$", &saveptr);
+	if (salt == NULL)
+		return NULL;	/* No salt! */
+	salted_password = apr_pstrcat(r->pool, salt, password, NULL);
+	if (salted_password == NULL)
+		return NULL;
+	apr_sha1_base64(salted_password, strlen(salted_password), sha1hash);
+	/* We want to skip over the "{SHA1}" in the hash and allow room for $S$...$...\0 */
+	final_hash = apr_pstrcat(r->pool, "$S$", salt, "$", &sha1hash[APR_SHA1PW_IDLEN], NULL);
+	return final_hash;
+}
+
+/*
+ * Yes, this method is weaker than a normal MD5 hash
+ */
+static char *auth_pg_netepi(request_rec *r, char *pw, char letter1, char letter2)
+{
+	static char *extpw;
+
+	extpw = apr_palloc(r->pool, 3 + strlen(pw));
+	if (extpw == NULL)
+		return NULL;
+	extpw[0] = letter1;
+	extpw[1] = letter2;
+	strcpy(&extpw[2], pw);
+	return auth_pg_md5(extpw);
+}
+
+char *netepi_flawed_pwd_check(request_rec *r, char *real_pw, char *sent_pw)
+{
+	char *l1, *l2;
+	char *netepi_pw;
+
+	for (l1 = netepi_pw_letters; *l1; l1++)
+		for (l2 = netepi_pw_letters; *l2; l2++) {
+			netepi_pw = auth_pg_netepi(r, sent_pw, *l1, *l2);
+			if (netepi_pw && !strcasecmp(real_pw, netepi_pw))
+				return netepi_pw;
+		}
+	return NULL;
+}
+
+char *netepi_pwd_check(pg_auth_config_rec *sec, request_rec *r, char *real_pw, char *sent_pw)
+{
+	char *netepi_pw;
+
+	if (real_pw == NULL || sent_pw == NULL)
+		return NULL;
+	/* If there's no $ at the front of the password it's an old password */
+	if (real_pw[0] != '$')
+		return sec->auth_pg_netepi_old_passwords ? netepi_flawed_pwd_check(r, real_pw, sent_pw) : NULL;
+	netepi_pw = netepi_crypt(r, sent_pw, real_pw);
+	return strcmp(netepi_pw, real_pw) ? NULL : netepi_pw;
+}
+
+
+/* Got from POstgreSQL 7.2 */
+/* ---------------
+ * Escaping arbitrary strings to get valid SQL strings/identifiers.
+ *
+ * Replaces "\\" with "\\\\" and "'" with "''".
+ * length is the length of the buffer pointed to by
+ * from.  The buffer at to must be at least 2*length + 1 characters
+ * long.  A terminating NUL character is written.
+ * ---------------
+ */
+
+static size_t pg_check_string(char *to, const char *from, size_t length)
+{
+	const char *source = from;
+	char *target = to;
+	unsigned int remaining = length;
+
+	while (remaining > 0) {
+		switch (*source) {
+		case '\\':
+			*target = '\\';
+			target++;
+			*target = '\\';
+			/* target and remaining are updated below. */
+			break;
+
+		case '\'':
+			*target = '\'';
+			target++;
+			*target = '\'';
+			/* target and remaining are updated below. */
+			break;
+
+		default:
+			*target = *source;
+			/* target and remaining are updated below. */
+		}
+		source++;
+		target++;
+		remaining--;
+	}
+
+	/* Write the terminating NUL character. */
+	*target = '\0';
+
+	return target - to;
+}
+
+
+/* Do a query and return the (0,0) value.  The query is assumed to be
+ * a select.
+ */
+char *do_pg_query(request_rec * r, char *query, pg_auth_config_rec * sec)
+{
+	PGresult *pg_result;
+	PGconn *pg_conn;
+	char *val;
+	char *result = NULL;
+
+	pg_errstr[0] = '\0';
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+	  "[mod_auth_pgsql.c] - do_pg_query - going to connect database \"%s\" ",
+	  sec->auth_pg_database);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	pg_conn = PQsetdbLogin(sec->auth_pg_host, sec->auth_pg_port,
+		sec->auth_pg_options, NULL, sec->auth_pg_database,
+		sec->auth_pg_user, sec->auth_pg_pwd);
+	if (PQstatus(pg_conn) != CONNECTION_OK) {
+		PQreset(pg_conn);
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "mod_auth_pgsql database connection error resetting %s",
+					 PQerrorMessage(pg_conn));
+		if (PQstatus(pg_conn) != CONNECTION_OK) {
+			apr_snprintf(pg_errstr, MAX_STRING_LEN,
+						 "mod_auth_pgsql database connection error reset failed %s",
+						 PQerrorMessage(pg_conn));
+			PQfinish(pg_conn);
+			return NULL;
+		}
+	}
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+				  "[mod_auth_pgsql.c] - do_pg_query - going to execute query \"%s\" ",
+				  query);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	pg_result = PQexec(pg_conn, query);
+
+	if (pg_result == NULL) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PGSQL 2: %s -- Query: %s ",
+					 PQerrorMessage(pg_conn), query);
+		PQfinish(pg_conn);
+		return NULL;
+	}
+
+	if (PQresultStatus(pg_result) == PGRES_EMPTY_QUERY) {
+		PQclear(pg_result);
+		PQfinish(pg_conn);
+		return NULL;
+	}
+
+	if (PQresultStatus(pg_result) != PGRES_TUPLES_OK) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN, "PGSQL 3: %s -- Query: %s",
+					 PQerrorMessage(pg_conn), query);
+		PQclear(pg_result);
+		PQfinish(pg_conn);
+		return NULL;
+	}
+
+	if (PQntuples(pg_result) == 1) {
+		val = PQgetvalue(pg_result, 0, 0);
+		if (val == NULL) {
+			apr_snprintf(pg_errstr, MAX_STRING_LEN, "PGSQL 4: %s",
+						 PQerrorMessage(pg_conn));
+			PQclear(pg_result);
+			PQfinish(pg_conn);
+			return NULL;
+		}
+
+		if (!(result = (char *) apr_pcalloc(r->pool, strlen(val) + 1))) {
+			apr_snprintf(pg_errstr, MAX_STRING_LEN,
+						 "Could not get memory for Postgres query.");
+			PQclear(pg_result);
+			PQfinish(pg_conn);
+			return NULL;
+		}
+
+		strcpy(result, val);
+	}
+
+	/* ignore errors here ! */
+	PQclear(pg_result);
+	PQfinish(pg_conn);
+	return result;
+}
+
+char *get_pg_pw(request_rec * r, char *user, pg_auth_config_rec * sec)
+{
+	char query[MAX_STRING_LEN];
+	char *safe_user;
+	int n;
+
+	safe_user = apr_palloc(r->pool, 1 + 2 * strlen(user));
+	pg_check_string(safe_user, user, strlen(user));
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+				  "[mod_auth_pgsql.c] - get_pg_pw - going to retrieve password for user \"%s\" from database",
+				  user);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	if ((!sec->auth_pg_pwd_table) ||
+		(!sec->auth_pg_pwd_field) || (!sec->auth_pg_uname_field)) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG: Missing parameters for password lookup: %s%s%s",
+					 (sec->auth_pg_pwd_table ? "" : "Password table "),
+					 (sec->
+					  auth_pg_pwd_field ? "" : "Password field name "),
+					 (sec->
+					  auth_pg_uname_field ? "" : "UserID field name "));
+		return NULL;
+	};
+
+	if (sec->auth_pg_lowercaseuid) {
+		/* and force it to lowercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (isupper(safe_user[n])) {
+				safe_user[n] = tolower(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+	if (sec->auth_pg_uppercaseuid) {
+		/* and force it to uppercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (islower(safe_user[n])) {
+				safe_user[n] = toupper(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+	n = apr_snprintf(query, MAX_STRING_LEN,
+					 "select %s from %s where %s='%s' %s",
+					 sec->auth_pg_pwd_field, sec->auth_pg_pwd_table,
+					 sec->auth_pg_uname_field, safe_user,
+					 sec->auth_pg_pwd_whereclause ? sec->
+					 auth_pg_pwd_whereclause : "");
+
+	if (n < 0 || n > MAX_STRING_LEN) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG: Detected SQL-truncation attack. Auth aborted.");
+		return NULL;
+	}
+	return do_pg_query(r, query, sec);
+}
+
+static char *get_pg_grp(request_rec * r, char *group, char *user,
+				 pg_auth_config_rec * sec)
+{
+	char query[MAX_STRING_LEN];
+	char *safe_user;
+	char *safe_group;
+	int n;
+
+	safe_user = apr_palloc(r->pool, 1 + 2 * strlen(user));
+	safe_group = apr_palloc(r->pool, 1 + 2 * strlen(group));
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+				  "[mod_auth_pgsql.c] - get_pg_grp - going to retrieve group for user \"%s\" from database",
+				  user);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	query[0] = '\0';
+	pg_check_string(safe_user, user, strlen(user));
+	pg_check_string(safe_group, group, strlen(group));
+
+	if ((!sec->auth_pg_grp_table) ||
+		(!sec->auth_pg_grp_group_field) || (!sec->auth_pg_grp_user_field))
+	{
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG: Missing parameters for password lookup: %s%s%s",
+					 (sec->auth_pg_grp_table ? "" : "Group table name"),
+					 (sec->
+					  auth_pg_grp_group_field ? "" :
+					  "GroupID field name "),
+					 (sec->
+					  auth_pg_grp_user_field ? "" :
+					  "Group table user field name "));
+		return NULL;
+	};
+
+	if (sec->auth_pg_lowercaseuid) {
+		/* and force it to lowercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (isupper(safe_user[n])) {
+				safe_user[n] = tolower(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+	if (sec->auth_pg_uppercaseuid) {
+		/* and force it to uppercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (islower(safe_user[n])) {
+				safe_user[n] = toupper(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+
+	n = apr_snprintf(query, MAX_STRING_LEN,
+					 "select %s from %s where %s='%s' and %s='%s' %s",
+					 sec->auth_pg_grp_group_field, sec->auth_pg_grp_table,
+					 sec->auth_pg_grp_user_field, safe_user,
+					 sec->auth_pg_grp_group_field, safe_group,
+					 sec->auth_pg_grp_whereclause ? sec->
+					 auth_pg_grp_whereclause : "");
+
+	if (n < 0 || n > MAX_STRING_LEN) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG: Detected SQL-truncation attack. Auth aborted.");
+		return NULL;
+	}
+
+	return do_pg_query(r, query, sec);
+}
+
+/* Process authentication request from Apache*/
+static int pg_authenticate_basic_user(request_rec * r)
+{
+	pg_auth_config_rec *sec =
+		(pg_auth_config_rec *) ap_get_module_config(r->per_dir_config,
+													&auth_pgsql_module);
+	char *val = NULL;
+	char *sent_pw, *real_pw;
+	int res;
+	char *user;
+
+	if ((res = ap_get_basic_auth_pw(r, (const char **) &sent_pw)))
+		return res;
+	user = r->user;
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+				  "[mod_auth_pgsql.c] - pg_authenticate_basic_user - going to auth user \"%s\" pass \"%s\" uri \"%s\"",
+				  user, sent_pw, r->unparsed_uri);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	/* if *password* checking is configured in any way, i.e. then
+	 * handle it, if not decline and leave it to the next in line..  
+	 * We do not check on dbase, group, userid or host name, as it is
+	 * perfectly possible to only do group control and leave
+	 * user control to the next guy in line.
+	 */
+	if ((!sec->auth_pg_pwd_table) && (!sec->auth_pg_pwd_field)) {
+		ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+					  "[mod_auth_pgsql.c] - missing configuration parameters");
+		return DECLINED;
+	}
+	pg_errstr[0] = '\0';
+
+	if (sec->auth_pg_cache_passwords
+		&& (!apr_is_empty_table(sec->cache_pass_table))) {
+		val = (char *) apr_table_get(sec->cache_pass_table, user);
+
+		if (val)
+			real_pw = val;
+		else
+			real_pw = get_pg_pw(r, user, sec);
+	} else
+		real_pw = get_pg_pw(r, user, sec);
+
+	if (!real_pw) {
+		if (pg_errstr[0]) {
+			res = HTTP_INTERNAL_SERVER_ERROR;
+		} else {
+			if (sec->auth_pg_authoritative) {
+				/* force error and access denied */
+				apr_snprintf(pg_errstr, MAX_STRING_LEN,
+							 "mod_auth_pgsql: Password for user %s not found (PG-Authoritative)",
+							 user);
+				ap_note_basic_auth_failure(r);
+				res = HTTP_UNAUTHORIZED;
+			} else {
+				/* allow fall through to another module */
+				return DECLINED;
+			}
+		}
+		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+		return res;
+	}
+
+	/* allow no password, if the flag is set and the password
+	 * is empty. But be sure to log this.
+	 */
+	if ((sec->auth_pg_nopasswd) && (!strlen(real_pw))) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "[mod_auth_pgsql.c] - Empty password accepted for user \"%s\"",
+					 user);
+		ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+		pg_log_auth_user(r, sec, user, sent_pw);
+		return OK;
+	};
+
+	/* if the flag is off however, keep that kind of stuff at
+	 * an arms length.
+	 */
+	if ((!strlen(real_pw)) || (!strlen(sent_pw))) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "[mod_auth_pgsql.c] - Empty password rejected for user \"%s\"",
+					 user);
+		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+		ap_note_basic_auth_failure(r);
+		return HTTP_UNAUTHORIZED;
+	};
+
+	if (sec->auth_pg_encrypted)
+		switch (sec->auth_pg_hash_type) {
+		case AUTH_PG_HASH_TYPE_MD5:
+			sent_pw = auth_pg_md5(sent_pw);
+			break;
+		case AUTH_PG_HASH_TYPE_CRYPT:
+			sent_pw = (char *) crypt(sent_pw, real_pw);
+			break;
+		case AUTH_PG_HASH_TYPE_BASE64:
+			sent_pw = auth_pg_base64(sent_pw);
+			break;
+		}
+
+
+	if (sec->auth_pg_hash_type == AUTH_PG_HASH_TYPE_NETEPI) {
+		char *netepi_pw;
+
+		if (netepi_pw = netepi_pwd_check(sec, r, real_pw, sent_pw))
+			goto netepi_ok;
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG user %s: password mismatch", user);
+		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+		ap_note_basic_auth_failure(r);
+		return HTTP_UNAUTHORIZED;
+	netepi_ok:
+		sent_pw = netepi_pw;
+
+	} else if ((sec->auth_pg_hash_type == AUTH_PG_HASH_TYPE_MD5
+		 || sec->auth_pg_hash_type == AUTH_PG_HASH_TYPE_BASE64
+		 || sec->auth_pg_pwdignorecase)
+		? strcasecmp(real_pw, sent_pw) : strcmp(real_pw, sent_pw)) {
+		apr_snprintf(pg_errstr, MAX_STRING_LEN,
+					 "PG user %s: password mismatch", user);
+		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+		ap_note_basic_auth_failure(r);
+		return HTTP_UNAUTHORIZED;
+	}
+
+	/*  store password in the cache */
+	if (sec->auth_pg_cache_passwords && !val && sec->cache_pass_table) {
+		if ((apr_table_elts(sec->cache_pass_table))->nelts >=
+			MAX_TABLE_LEN) {
+			apr_table_clear(sec->cache_pass_table);
+		}
+		apr_table_set(sec->cache_pass_table, user, real_pw);
+	}
+
+	pg_log_auth_user(r, sec, user, sent_pw);
+	return OK;
+}
+
+/* Checking ID */
+
+static int pg_check_auth(request_rec * r)
+{
+	pg_auth_config_rec *sec =
+		(pg_auth_config_rec *) ap_get_module_config(r->per_dir_config,
+													&auth_pgsql_module);
+	char *user = r->user;
+	int m = r->method_number;
+	int group_result = DECLINED;
+
+
+
+	apr_array_header_t *reqs_arr = (apr_array_header_t *) ap_requires(r);
+	require_line *reqs = reqs_arr ? (require_line *) reqs_arr->elts : NULL;
+
+	register int x, res;
+	const char *t;
+	char *w;
+
+	pg_errstr[0] = '\0';
+
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+				  "[mod_auth_pgsql.c] - pg_check_auth - going to check auth for user \"%s\" ",
+				  user);
+#endif							/* DEBUG_AUTH_PGSQL */
+
+
+
+	/* if we cannot do it; leave it to some other guy 
+	 */
+	if ((!sec->auth_pg_grp_table) && (!sec->auth_pg_grp_group_field)
+		&& (!sec->auth_pg_grp_user_field))
+		return DECLINED;
+
+	if (!reqs_arr) {
+		if (sec->auth_pg_authoritative) {
+			/* force error and access denied */
+			apr_snprintf(pg_errstr, MAX_STRING_LEN,
+						 "mod_auth_pgsql: user %s denied, no access rules specified (PG-Authoritative)",
+						 user);
+			ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+			ap_note_basic_auth_failure(r);
+			res = HTTP_UNAUTHORIZED;
+		} else {
+			return DECLINED;
+		}
+	}
+
+	for (x = 0; x < reqs_arr->nelts; x++) {
+
+		if (!(reqs[x].method_mask & (1 << m)))
+			continue;
+
+		t = reqs[x].requirement;
+		w = ap_getword(r->pool, &t, ' ');
+
+		if (!strcmp(w, "valid-user"))
+			return OK;
+
+		if (!strcmp(w, "user")) {
+			while (t[0]) {
+				w = ap_getword_conf(r->pool, &t);
+				if (!strcmp(user, w))
+					return OK;
+			}
+			if (sec->auth_pg_authoritative) {
+				/* force error and access denied */
+				apr_snprintf(pg_errstr, MAX_STRING_LEN,
+							 "mod_auth_pgsql: user %s denied, no access rules specified (PG-Authoritative)",
+							 user);
+				ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+				ap_note_basic_auth_failure(r);
+				return HTTP_UNAUTHORIZED;
+			}
+
+		} else if (!strcmp(w, "group")) {
+			/* look up the membership for each of the groups in the table */
+			pg_errstr[0] = '\0';
+
+			while (t[0]) {
+				if (get_pg_grp(r, ap_getword(r->pool, &t, ' '), user, sec)) {
+					group_result = OK;
+				};
+			};
+
+			if (pg_errstr[0]) {
+				ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+				return HTTP_INTERNAL_SERVER_ERROR;
+			}
+
+			if (group_result == OK)
+				return OK;
+
+			if (sec->auth_pg_authoritative) {
+				apr_snprintf(pg_errstr, MAX_STRING_LEN,
+							 "[mod_auth_pgsql.c] - user %s not in right groups (PG-Authoritative)",
+							 user);
+				ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_auth_pgsql.c] - ERROR - %s", pg_errstr);
+				ap_note_basic_auth_failure(r);
+				return HTTP_UNAUTHORIZED;
+			};
+		}
+	}
+
+	return DECLINED;
+}
+
+
+/* Send the authentication to the log table */
+int
+pg_log_auth_user(request_rec * r, pg_auth_config_rec * sec, char *user,
+				 char *sent_pw)
+{
+	char sql[MAX_STRING_LEN];
+	char *s;
+	int n;
+	char fields[MAX_STRING_LEN];
+	char values[MAX_STRING_LEN];
+	char *safe_user;
+	char *safe_pw;
+	char *safe_req;
+	char ts[MAX_STRING_LEN];	/* time in string format */
+	apr_time_exp_t t;			/* time of request start */
+	apr_size_t retsize;
+
+	safe_user = apr_palloc(r->pool, 1 + 2 * strlen(user));
+	safe_pw = apr_palloc(r->pool, 1 + 2 * strlen(sent_pw));
+	safe_req = apr_palloc(r->pool, 1 + 2 * strlen(r->the_request));
+
+	/* we do not want to process internal redirect  */
+	if (!ap_is_initial_req(r))
+		return DECLINED;
+	if ((!sec->auth_pg_log_table) || (!sec->auth_pg_log_uname_field) || (!sec->auth_pg_log_date_field)) {	// At least table name, username and date field are specified
+		// send error message and exit 
+		return DECLINED;
+	}
+
+	/* AUD: MAX_STRING_LEN probably isn't always correct */
+	pg_check_string(safe_user, user, strlen(user));
+	pg_check_string(safe_pw, sent_pw, strlen(sent_pw));
+	pg_check_string(safe_req, r->the_request, strlen(r->the_request));
+
+
+	if (sec->auth_pg_lowercaseuid) {
+		/* and force it to lowercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (isupper(safe_user[n])) {
+				safe_user[n] = tolower(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+	if (sec->auth_pg_uppercaseuid) {
+		/* and force it to uppercase */
+		n = 0;
+		while (safe_user[n] && n < (MAX_STRING_LEN - 1)) {
+			if (islower(safe_user[n])) {
+				safe_user[n] = toupper(safe_user[n]);
+			}
+			n++;
+		}
+	}
+
+
+	/* time field format  */
+	apr_time_exp_lt(&t, r->request_time);
+	apr_strftime(ts, &retsize, 100, "%Y-%m-%d %H:%M:%S", &t);
+
+
+
+	/* SQL Statement, required fields: Username, Date */
+	apr_snprintf(fields, MAX_STRING_LEN, "%s,%s",
+				 sec->auth_pg_log_uname_field,
+				 sec->auth_pg_log_date_field);
+	apr_snprintf(values, MAX_STRING_LEN, "'%s','%s'", safe_user, ts);
+
+	/* Optional parameters */
+	if (sec->auth_pg_log_addrs_field) {	/* IP Address field */
+		apr_snprintf(sql, MAX_STRING_LEN, ", %s",
+					 sec->auth_pg_log_addrs_field);
+		strncat(fields, sql, MAX_STRING_LEN - strlen(fields) - 1);
+		apr_snprintf(sql, MAX_STRING_LEN, ", '%s'",
+					 r->connection->remote_ip);
+		strncat(values, sql, MAX_STRING_LEN - strlen(values) - 1);
+	}
+	if (sec->auth_pg_log_pwd_field) {	/* Password field , clear WARNING */
+		apr_snprintf(sql, MAX_STRING_LEN, ", %s",
+					 sec->auth_pg_log_pwd_field);
+		strncat(fields, sql, MAX_STRING_LEN - strlen(fields) - 1);
+		apr_snprintf(sql, MAX_STRING_LEN, ", '%s'", safe_pw);
+		strncat(values, sql, MAX_STRING_LEN - strlen(values) - 1);
+	}
+	if (sec->auth_pg_log_uri_field) {	/* request string */
+		apr_snprintf(sql, MAX_STRING_LEN, ", %s",
+					 sec->auth_pg_log_uri_field);
+		strncat(fields, sql, MAX_STRING_LEN - strlen(fields) - 1);
+		apr_snprintf(sql, MAX_STRING_LEN, ", '%s'", safe_req);
+		strncat(values, sql, MAX_STRING_LEN - strlen(values) - 1);
+	}
+
+	apr_snprintf(sql, MAX_STRING_LEN, "insert into %s (%s) values(%s) ; ",
+				 sec->auth_pg_log_table, fields, values);
+
+	s = do_pg_query(r, sql, sec);
+	return (0);
+}
+
+static int
+pg_auth_init_handler(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp,
+					 server_rec * s)
+{
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p,
+				  "[mod_auth_pgsql.c] - pg_auth_init_handler -  ");
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	ap_add_version_component(p, "mod_auth_pgsql/" AUTH_PGSQL_VERSION);
+	return OK;
+}
+
+/* Init the module private memory pool, used for the per directory cache tables */
+static void *pg_auth_server_config(apr_pool_t * p, server_rec * s)
+{
+#ifdef DEBUG_AUTH_PGSQL
+	ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, p,
+				  "[mod_auth_pgsql.c] - pg_auth_server_config -  ");
+#endif							/* DEBUG_AUTH_PGSQL */
+
+	if (auth_pgsql_pool == NULL)
+		apr_pool_create_ex(&auth_pgsql_pool, NULL, NULL, NULL);
+
+	return OK;
+}
+
+
+static void register_hooks(apr_pool_t * p)
+{
+	ap_hook_post_config(pg_auth_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
+	ap_hook_auth_checker(pg_check_auth, NULL, NULL, APR_HOOK_MIDDLE);
+	ap_hook_check_user_id(pg_authenticate_basic_user, NULL, NULL,
+						  APR_HOOK_MIDDLE);
+};
+
+module AP_MODULE_DECLARE_DATA auth_pgsql_module = {
+	STANDARD20_MODULE_STUFF,
+	create_pg_auth_dir_config,	/* dir config creater */
+	NULL,						/* dir merger --- default is to override */
+	pg_auth_server_config,		/* server config */
+	NULL,						/* merge server config */
+	pg_auth_cmds,				/* command table */
+	register_hooks				/* Apache2 register hooks */
+};
diff --git a/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.html b/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.html
new file mode 100644
index 0000000..0f5acfc
--- /dev/null
+++ b/tools/mod_auth_pgsql-2.0.3-netepi.4101/mod_auth_pgsql.html
@@ -0,0 +1,608 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+  <meta http-equiv="Content-Type"
+ content="text/html; charset=iso-8859-1">
+  <meta name="GENERATOR"
+ content="Mozilla/4.05 [en] (X11; I; Linux 2.0.34 i686) [Netscape]">
+  <title>Module mod_auth_pgsql PostgreSQL authentication module fro
+Apache web server</title>
+</head>
+<body>
+<h1>Apache2 Module mod_auth_pgsql</h1>
+<h2> PostgreSQL Authentication </h2>
+This module allows user authentication (and can log authethication
+requests) against information stored in a <a
+ href="http://www.postgresql.org/"> PostgreSQL</a> database. PostgreSQL
+is a public domain SQL database.
+<ul>
+  <li>Authentication</li>
+</ul>
+<pre>	One database, and one (or two) tables.  One table holds the username and the encryped (or plain) password.<br> 	The other table holds the username and the names of the group to which the user belongs.  <br>	It is possible to have username, groupname and password in the same table. <br></pre>
+<ul>
+  <li>Access Logging</li>
+</ul>
+<pre>	Every authentication access is logged in the same database of the authentication table, but in different table.<br>	User name and date of the request are logged.<br>	As option, it can log <i>password, </i><i>ip address</i>, <i>request lin</i>e.<br></pre>
+<p><br>
+</p>
+<p><br>
+This page documents version 2.0.3 (2006 01 05) of mod_auth_pgsql
+require Apache >= 2.0.40 and PostreSQL >=  7.x <br>
+If you need to authenticate with Apache 1.3.x please use the old
+version (0.9.12), this one will not work.<br>
+</p>
+<p><br>
+Home Page <a href="http://www.giuseppetanzilli.it/mod_auth_pgsql2/">http://www.giuseppetanzilli.it/mod_auth_pgsql2/</a><br>
+</p>
+<p>Old module for Apache 1.3 :  <a
+ href="http://www.giuseppetanzilli.it/mod_auth_pgsql/">http://www.giuseppetanzilli.it/mod_auth_pgsql/</a><br>
+</p>
+<p><br>
+</p>
+<p><a href="#Directives">Module Directives </a> | <a href="#Download">
+Download </a> | <a href="#compile">Compilation & Installation
+Notes</a> | <a href="#example">Example</a> | <a href="#notes">Technical
+Notes</a> |<a href="#Changelog"> Changelog</a> </p>
+<h2><a name="Directives"></a> Directives</h2>
+<ul>
+  <li><a href="#host">Auth_PG_host</a> </li>
+  <li><a href="#port">Auth_PG_port</a> </li>
+  <li><a href="#options">Auth_PG_options</a> </li>
+  <li><a href="#pwd_table">Auth_PG_database</a> </li>
+  <li><a href="#PGuser">Auth_PG_user</a> </li>
+  <li><a href="PGpwd">Auth_PG_pwd</a> </li>
+  <li><a href="#grp_table">Auth_PG_pwd_table</a> </li>
+  <li><a href="#uid_field">Auth_PG_grp_table</a> </li>
+  <li><a href="#uid_field">Auth_PG_uid_field</a> </li>
+  <li><a href="#pwd_field">Auth_PG_pwd_field</a> </li>
+  <li><a href="#gid_field">Auth_PG_gid_field</a> </li>
+  <li><a href="#nopasswd">Auth_PG_nopasswd</a> </li>
+  <li><a href="#authoritative">Auth_PG_authoritative</a> </li>
+  <li><a href="#lowercaseuid">Auth_PG_lowercase_uid</a> </li>
+  <li><a href="#uppercaseuid">Auth_PG_uppercase_uid</a></li>
+  <li><a href="#pwd_ignore_case">Auth_PG_pwd_ignore_case</a><br>
+  </li>
+  <li><a href="#encrypted">Auth_PG_encrypted</a> </li>
+  <li><a href="#hash_type">Auth_PG_hash_type</a> <br>
+  </li>
+  <li><a href="#pwd_whereclause">Auth_PG_pwd_whereclause</a> </li>
+  <li><a href="#grp_whereclause">Auth_PG_grp_whereclause</a></li>
+  <li><a href="#cache_passwords">Auth_PG_cache_passwords</a><br>
+  </li>
+  <li><a href="#log_table">Auth_PG_log_table</a> </li>
+  <li><a href="#log_uname_field">Auth_PG_log_uname_field</a> </li>
+  <li><a href="#log_date_field">Auth_PG_log_date_field</a> </li>
+  <li><a href="#log_uri_field">Auth_PG_log_uri_field</a> </li>
+  <li><a href="#log_addrs_field">Auth_PG_log_addrs_field</a> </li>
+  <li><a href="#log_pwd_field">Auth_PG_log_pwd_field</a> </li>
+</ul>
+<hr>
+<h2><a name="host"></a> Auth_PG_host</h2>
+<b>Syntax:</b> Auth_PG_host <i>hostname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the host on which the <b>postmaster</b> is running. It is
+optional, if not specified unix socket will be used.<br>
+The effective uid of the server should be allowed
+access, otherwise a trusted user or user with password must
+be specified. <br>
+</p>
+<h2><a name="port"></a> Auth_PG_port</h2>
+<b>Syntax:</b> Auth_PG_port <i>port number</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the TCP/IP port number at which the <b>postmaster</b> can
+be found. </p>
+<h2><a name="options"></a> Auth_PG_options</h2>
+<b>Syntax:</b> Auth_PG_options <i>option string</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies an option string to be passed to the postgreSQL backend
+process. Refer to the PostgreSQL user manual for a description of the
+available options. </p>
+<h2><a name="database"></a> Auth_PG_database</h2>
+<b>Syntax:</b> Auth_PG_database <i>database name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the name of the database that stores the authentication
+information.<br>
+<br>
+</p>
+<h2><a name="PGuser"></a> Auth_PG_user<br>
+</h2>
+<b>Syntax:</b> Auth_PG_user <i>username</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension <br>
+<br>
+Specifies the database username who access the PostgreSQL,
+should have read access on all authetication tables, and write
+access on all the log tables (if used).<br>
+Needed if the user who make the quey is differrent from the
+user runnig apache, or if the posmater is on a different server and you
+must autheticate with password<br>
+<h2><a name="PGpwd"></a> Auth_PG_pwd</h2>
+<p><b>Syntax:</b> Auth_PG_pwd <i>password</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension <br>
+<br>
+Specifies the user password for the user who access the
+PostgreSQL.<br>
+Needed if the user specified with Auth_PG_user is not trusted.<br>
+</p>
+<p><br>
+</p>
+<h2><a name="pwd_table"></a> Auth_PG_pwd_table</h2>
+<b>Syntax:</b> Auth_PG_pwd_table <i>relation name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Gives the name of the relation which contains the username and
+password information.<br>
+See Auth_PG_encrypted, by default the password is encrypted.<br>
+</p>
+<h2><a name="grp_table"></a> Auth_PG_grp_table</h2>
+<b>Syntax:</b> Auth_PG_grp_table <i>relation name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Gives the name of the relation which contains the username and group
+information. This can be the same table specified with
+Auth_PG_pwd_table. This directive is only necessary if you
+want to authenticate by user groups. A user within multiple
+groups has therefore multiple entries. </p>
+<h2><a name="uid_field"></a> Auth_PG_uid_field</h2>
+<b>Syntax:</b> Auth_PG_uid_field <i>attribute name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the attribute name of the field containing the user name
+in the Auth_PG_pwd_table relation. </p>
+<h2><a name="pwd_field"></a> Auth_PG_pwd_field</h2>
+<b>Syntax:</b> Auth_PG_pwd_field <i>attribute name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the attribute name of the field containing the encrypted
+(see Auth_PG_encrypted) password in the Auth_PGpwd_table relation.<br>
+Please remember to use field of type varchar, not char for the password.<br>
+</p>
+<h2><a name="gid_field"></a> Auth_PG_gid_field</h2>
+<b>Syntax:</b> Auth_PG_gid_field <i>attribute name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Specifies the attribute name of the field containing the group name
+in the Auth_PG_grp_table relation. This directive is only necessary if
+you want to authenticate by user groups. </p>
+<h2><a name="nopasswd"></a> Auth_PG_nopasswd</h2>
+<b>Syntax:</b> Auth_PG_nopasswd <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option is off by default. Turning it on will cause a user to be
+validated when their password field is empty. The password entered will
+be ignored. Exercise caution when turning this on. </p>
+<h2><a name="authoritative"></a> Auth_PG_authoritative</h2>
+<b>Syntax:</b> Auth_PG_authoritative <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option is on by default. Turning it off will cause low level
+errors such a user not being found or a simple configuration error to
+fall through to other authentication directives which may be defined
+for this area. For example, if a parent directory has
+another authorization scheme and a user name is not found for
+the PostgreSQL scheme, the parent directory scheme will be given
+the chance to try and authenticate the user. Exercise caution
+when turning this option off. It can be a security risk. Can be
+used to use two authentication schemes for the same dir. </p>
+<h2><a name="lowercaseuid"></a> Auth_PG_lowercase_uid</h2>
+<b>Syntax:</b> Auth_PG_lowercase_uid <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option is off by default. Controls whether this module converts
+user UIDs to lowercase before looking them up. When turned on this does
+not affect the case of the original user ID should this module decline
+to authenticate and a lower level is called.</p>
+<h2><a name="uppercaseuid"></a> Auth_PG_uppercase_uid</h2>
+<b>Syntax:</b> Auth_PG_uppercase_uid <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option is off by default. Controls whether this module converts
+user UIDs to uppercase before looking them up. When turned on this does
+not affect the case of the original user ID should this module decline
+to authenticate and a lower level is called.</p>
+<h2><a name="pwd_ignore_case"></a> Auth_PG_pwd_ignore_case</h2>
+<b>Syntax:</b> Auth_PG_pwd_ignore_case <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option is off by default. Controls whether this module ignore
+the upper/lowercase of password from user, when looking up clear text
+password from db. </p>
+<h2><a name="encrypted"></a> Auth_PG_encrypted</h2>
+<b>Syntax:</b> Auth_PG_encrypted <i>on</i> or <i>off</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Defaults to on. Controls weather this module expects passwords in
+the database to be encrypted or not. When turned off, you can use
+unencrypted passwords in your database. Exercise caution when deciding
+to turn this off!</p>
+<p><br>
+</p>
+<h2><a name="hash_type"></a> Auth_PG_hash_type</h2>
+<b>Syntax:</b> Auth_PG_hash_type CRYPT or MD5 or BASE64 or NETEPI<br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>Set the encryption type for the password stored in the database.<br>
+Defaults to CRYPT.  </p>
+<p><br>
+</p>
+<h2><a name="pwd_whereclause"></a> Auth_PG_pwd_whereclause</h2>
+<b>Syntax:</b> Auth_PG_pwd_whereclause <i>SQL fragment</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option allows you to exercise greater control over the SQL code
+used to retrieve the user name and password from the database. You can
+use this to search for the username using more attributes
+in the table than the pwd_field. </p>
+<p>The basic SQL statement used to retrieve a user's password for
+checking looks like this: </p>
+<ul>
+select <i><pwd_field></i> from <i><pwd_table></i> where <i>
+<uid_field></i> ='<i><remote_user></i> '
+</ul>
+<p>The pwd_whereclause will be added to the end of this statement and
+must fit logically. <em>The where clause must be double quoted, with
+initial space .</em></p>
+<p>Example: </p>
+<pre>      Auth_PG_pwd_whereclause " and access_level > 100 "</pre>
+<p> </p>
+<h2><a name="grp_whereclause"></a> Auth_PG_grp_whereclause</h2>
+<b>Syntax:</b> Auth_PG_grp_whereclause <i>SQL fragment</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension
+<p>This option allows you to exercise greater control over the SQL code
+used to retrieve the group name and corresponding user from the
+database. You can use this to search for the group name
+using more attributes in the table than the gid_field. </p>
+<p>The basic SQL statement used to retrieve a group name and user name
+for checking looks like this: </p>
+<ul>
+select <i><uid_field></i> from <i><grp_table></i> where <i>
+<gid_field></i> ='<i><required group></i> '
+</ul>
+The gid_whereclause will be added to the end of this statement
+and must fit logically. <em>The where clause must be double
+quoted.<br>
+<br>
+<br>
+</em>
+<h2><a name="log_table"></a> Auth_PG_cache_passwords</h2>
+<b>Syntax:</b> Auth_PG_cache_passwords on | off<br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Enable password caching, default is off. <br>
+The cache table is local to the directory, will be cleaned when full,
+default size is 50.<br>
+<br>
+<h2><a name="log_table"></a> Auth_PG_log_table</h2>
+<b>Syntax:</b> Auth_PG_log_table <i>table name</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the table where logging information will go, the user
+need write access to this table.<br>
+Required for logging.<br>
+<br>
+<h2><a name="log_uname_field"></a> Auth_PG_log_uname_field</h2>
+<b>Syntax:</b> Auth_PG_log_uname_field <i>fieldname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the fieldname where the username will be saved.<br>
+Required for logging.<br>
+<br>
+<h2><a name="log_date_field"></a> Auth_PG_log_date_field</h2>
+<b>Syntax:</b> Auth_PG_log_date_field <i>fieldname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the fieldname where the date will be saved.<br>
+Required for logging.<br>
+<br>
+<h2><a name="log_uri_field"></a> Auth_PG_log_uri_field</h2>
+<b>Syntax:</b> Auth_PG_uri_field <i>fieldname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the fieldname where the request string will be saved.<br>
+Optional for logging.<br>
+<h2><a name="log_addrs_field"></a> Auth_PG_log_addrs_field</h2>
+<b>Syntax:</b> Auth_PG_addrs_field  <i>fieldname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the fieldname where the IP address will be saved.<br>
+Optional for logging.<br>
+<h2><a name="log_pwd_field"></a> Auth_PG_log_pwd_field</h2>
+<b>Syntax:</b> Auth_PG_pwd_field  <i>fieldname</i><br>
+<b>Context:</b> directory, .htaccess <br>
+<b>Override:</b> AuthConfig <br>
+<b>Status:</b> Extension<br>
+<br>
+Specifies the fileld name where the password used to authenticate
+will be saved.<br>
+<b>Note: the password will be saved in clear text</b><br>
+Optional for logging.<br>
+<br>
+<br>
+<hr>
+<h2><a name="example"></a> Example</h2>
+Here is an example <b>.htaccess</b> file you might use to enable
+PostgreSQL authentication:
+<pre><br>PostgreSQL trusted user:<br><br>	AuthName "My PostgreSQL Authenticator"<br>	AuthType basic<br><br>	Auth_PG_host localhost<br>	Auth_PG_port 5432<br>	Auth_PG_user postgres<br>	Auth_PG_database www<br>	Auth_PG_pwd_table valid_users<br>	Auth_PG_uid_field user<br>	Auth_PG_pwd_field password<br><br>	<LIMIT GET POST><br>		require valid-user<br>	</LIMIT><br><br>PostgreSQL trusted user, select only users with access_level > 100:<br><br>	AuthName "My PostgreSQL Authenticato [...]
+<h2><a name="notes"></a> Technical Notes</h2>
+<ul>
+  <li> If the SQL statement used to retrieve the user or group name
+returns more than one tuple, it is considered an error. If this is
+likely to happen, use Auth_PG_pwd_whereclause and
+Auth_PG_grp_whereclause to create a query that will only return one
+tuple.<br>
+  </li>
+</ul>
+<br>
+<br>
+<br>
+<br>
+<h2><a name="Download"></a> Download </h2>
+<ul>
+  <li> Latest release:<br>
+    <a href="http://www.giuseppetanzilli.it/mod_auth_pgsql2/dist/">http://www.giuseppetanzilli.it</a>
+    <a href="http://www.giuseppetanzilli.it/mod_auth_pgsql2/dist/">
+/mod_auth_pgsql2/dist/</a> </li>
+  <li>Homepage and Documentation:<br>
+    <a href="http://www.giuseppetanzilli.it/mod_auth_pgsql2/">http://www.giuseppetanzilli.it/mod_auth_pgsql2</a>
+  </li>
+</ul>
+<br>
+<br>
+<br>
+<br>
+<h2><a name="compile"></a> Compilation & Installation Notes</h2>
+<b>From the INSTALL file:</b>
+<pre>PXS DSO Install:<br>	- untar mod_auth_pgsql  <br>		cd /usr/local/src<br>		tar zxf mod_auth_pgsql-2.0.x.tar.gz<br>		cd mod_auth_pgsql-2.0.x<br>	- make & install as DSO<br>	 	<br>	 	/usr/local/apache2/bin/apxs  -i -a -c -I /usr/local/pgsql/include -L /usr/local/pgsql/lib -lpq mod_auth_pgsql.c<br> <br>	<br><br>STATIC Install<br><br><br>	- untar mod_auth_pgsql  <br>		cd /usr/local/src<br>		tar zxf mod_auth_pgsql-2.0.x.tar.gz<br><br>	- untar apache source and run ./configure from the [...]
+<h2><a name="Changelog"></a> Changelog </h2>
+ Version 0.0  (Feb 1996) First release (adaptation from
+mod_auth_msql.c v0.5)<br>
+              
+mod_auth_msql.c was written by Dirk.vanGulik at jrc.it;<br>
+    0.1  (Mar 1996) Correct PGgid_field command<br>
+    0.2  (Mar 1996) Added use and
+Auth_PGgrp_whereclause<br>
+    0.3  (May 1996) Some sundry patches to fix
+grp_whereclause and some compilation<br>
+              
+issues.  Thanks to Randy Terbrush.<br>
+    0.4  (Jun 1996) Got rid of pg_set_string_slot,
+use the stock function in http_config.c<br>
+              
+Made Auth_PGoptions take an actual string argument.<br>
+              
+Fixed some logical sillyness in check_auth<br>
+              
+Made command error strings more reasonable.<br>
+              
+Try to weed out possibility of function names conflicting<br>
+           
+     with other 3rd party modules.<br>
+              
+Added Auth_PG_authorative directive<br>
+              
+Added Auth_PG_enrypted directive<br>
+    0.5  (Dec 1996) Some cosmetic changes to
+make apache 1.2 happy.<br>
+              
+Added correctly spelled Auth_PG_Authoratative command.<br>
+    0.6    (13 July 1998) Renamed to
+mod_auth_pgsql <br>
+           
+    Some changes just to make it compile under apache 1.3<br>
+           
+    tested on apache 1.3 postgreSQL 6.4<br>
+           
+    Giuseppe Tanzilli g.tanzilli at eurolink.it<br>
+    0.7  (11 November 1998)    <br>
+           
+    Some little changes just to make it compile in<br>
+           
+    Apache 1.3.3 & PostgreSQL 6.4<br>
+           
+    the Where clause commands now need double quote<br>
+<br>
+    0.7.1  02 March 1999<br>
+            - now
+compile with Apache 1.3.3 & PostgreSQL 6.4<br>
+            - now
+configuration tag in apache for where clause need double quote <br>
+            - use the
+APACI configuration script<br>
+            -
+APACI-Configuration with 1.3.4, additional-Server-Info-String<br>
+<br>
+    0.7.2  25 June 1999<br>
+            - now
+compile with Apache 1.3.6 & PostgreSQL 6.5<br>
+<br>
+    0.8.0  8 August 1999<br>
+            - now
+apache 1.3.1 and PostgreSQL 6.5 are needed<br>
+            Added
+options to access authenticated database<br>
+            Added
+options to log the authentication requests <br>
+<br>
+<br>
+    0.9.0 1999-10-03 (Tollef Fog Heen
+<tollef at add.no>)<br>
+            - tested
+on apache 1.3.9 and PostgreSQL 6.5.3<br>
+            -
+added autoconf-script<br>
+               -
+shared module support<br>
+ <br>
+     0.9.1 2000-03-31 (Tollef Fog Heen
+<tollef at add.no>)<br>
+         - fixed some minor bugs
+related to autoconf (we don't need expat)<br>
+ <br>
+     0.9.2 2000-05-21 (Hayato UENOHARA
+<uenohara at ueda.info.waseda.ac.jp>)<br>
+         - Original by Matthias
+Eckermann <eckerman at lrz.uni-muenchen.de><br>
+         - I only copied it from
+a lost branch version 0.7.1 (gone for some reason)<br>
+         - So all credit goes to
+Matthias :-)<br>
+         - added
+'AUTH_PGSQL_VERSION' & 'pg_auth_init_handler'<br>
+ <br>
+     0.9.5 2000-06-04 (Voloda
+<vladimir.kloz at dtg.cz>)<br>
+         
+    - added options Auth_PGuser, Auth_PGpwd <br>
+ <br>
+      0.9.6 2001-08-27 (Giuseppe Tanzilli
+<info at giuseppetanzilli.it>)<br>
+             - ported
+configure & install to Apache 1.3.20 and PostgreSQL 7.1.x<br>
+             -
+replaced sprintf with snprintf thanks to Erik Rossen
+<rossen at freesurf.ch><br>
+             -
+changed some directive names to make all them like Auth_PG_*<br>
+             
+  UPDATE YOUR CONFIG FILES<br>
+             -
+Added Logging feature from 0.8 source<br>
+             -
+Check input string from user, to deny sql string attack, see report:<br>
+           
+         
+http://cert.uni-stuttgart.de/advisories/apache_auth.php <br>
+             -
+Updated html documentation<br>
+<br>
+    0.9.7     2001-09-02  (Giuseppe
+Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+     - fix Auth_PG_pwd_whereclause &
+Auth_PG_grp_whereclause   <br>
+    0.9.8     2001-09-03   (
+Andrei Nigmatulin  <anight at mail.ru>)<br>
+              
+  - Added Auth_PG_hash_type, now we support crypt and md5 password
+hash method <br>
+    0.9.9     2001-09-25
+ (Giuseppe Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+     - fix security problem<br>
+           
+     - added password caching, default is off,
+please check it and report by mail<br>
+           
+        it will be useful for hi load
+sites<br>
+    0.9.10   2001-10-11  (Giuseppe
+Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+     - fix MD5 auth failures in some cases<br>
+    0.9.11   2001-11-20  (Giuseppe Tanzilli
+<info at giuseppetanzilli.it>)<br>
+           
+      - security fix: string buffers
+ Sebastian
+Krahmer <krahmer at suse.de>  and Andreas Hasenack
+<andreas at conectiva.com.br><br>
+           
+     - added Auth_PG_lowercase_uid option  
+ (Bernard Quatermass <bernard at quatermass.co.uk>)<br>
+           
+     - added Auth_PG_uppercase_uid option  <br>
+           
+     - added --with-pgsql-lib=<dir> and
+--with-pgsql-include=<dir> to configure, to make happy debian
+users<br>
+    0.9.12    2002-01-11 (Giuseppe
+Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+       - small fix for
+Auth_PG_uppercase_uid/Auth_PG_lowercase_uid<br>
+           
+       - added Auth_PG_pwd_ignore_case option <br>
+    2.0.0       2003-01-30
+(Giuseppe Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+       - ported to Apache 2.0<br>
+           
+       - fixes from Gary Benson
+<gbenson at redhat.com> <br>
+           
+       - fixes from Guenter  Knauf
+<info at gknw.de><br>
+           
+       - added debug feature, compile time
+option, do not use it on production systems<br>
+           
+       - added Auth_PG_grp_user_field<br>
+           
+       - renamed Auth_PG_gid_field to
+Auth_PG_grp_group_field<br>
+           
+       - added BASE64 support, thanks to
+Norikatsu Shigemura <n-shigemura at ensure-tech.co.jp><br>
+           
+       - now we reuse database
+connection,  2x speedup !!   <br>
+     2.0.1      
+2003-02-21 (Giuseppe Tanzilli <info at giuseppetanzilli.it>)<br>
+               
+   - fix bug introduced with connection reuse<br>
+    2.0.3       2006-01-05
+(Giuseppe Tanzilli <info at giuseppetanzilli.it>)<br>
+           
+        - Security fix from iDefense
+Security Advisory [IDEF1245]<br>
+               
+   - many bug fix<br>
+    2.0.3+netepi       2009-06-11
+(James Farrow <james at fn.com.au>)<br>
+           
+       - added NETEPI hash support<br>
+<br>
+<br>
+<br>
+<br>
+<br>
+</body>
+</html>
diff --git a/tools/pgcasttest.py b/tools/pgcasttest.py
new file mode 100644
index 0000000..cb4bc32
--- /dev/null
+++ b/tools/pgcasttest.py
@@ -0,0 +1,59 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+# Test implicit PostgreSQL casts
+
+from pyPgSQL import PgSQL
+PgSQL.useUTCtimeValue = True            # Works around brokeness in some vers.
+from mx import DateTime
+
+def test(db, from_type, value, to_type, expect):
+    curs = db.cursor()
+    try:
+        curs.execute('create table pgcasttest (a %s, b %s)' % 
+                     (from_type, to_type))
+        curs.execute('insert into pgcasttest (a) values (%s)', (value,))
+        curs.execute('insert into pgcasttest (b) select a from pgcasttest');
+        curs.execute('select b from pgcasttest where a is null');
+        result = curs.fetchall()
+        assert len(result) == 1, 'result %r' % result
+        assert result[0][0] == expect, 'result %r' % result
+    finally:
+        curs.close()
+        db.rollback()
+
+db = PgSQL.connect(database='test')
+test(db, 'varchar', 'a', 'text', 'a')
+test(db, 'text', 'a', 'varchar', 'a')
+test(db, 'float', 1.1, 'varchar', '1.1')
+test(db, 'float', 1.1, 'text', '1.1')
+#test(db, 'float', 1.1e99, 'varchar(1)', '1.1e99')
+#test(db, 'text', '1.1', 'float', 1.1)
+#test(db, 'bool', True, 'text', 'true')
+#test(db, 'bool', True, 'int', 1)
+test(db, 'float', 1.1, 'int', 1)
+test(db, 'float', 1.5, 'int', 2)
+test(db, 'int', 1, 'float', 1.0)
+test(db, 'date', '2002-3-4', 'text', '2002-03-04')
+test(db, 'date', '2002-3-4', 'varchar', '2002-03-04')
+test(db, 'date', '2002-3-4', 'timestamp', DateTime.DateTime(2002,3,4))
+test(db, 'timestamp', '2002-3-4 5:6:7', 'text', '2002-03-04 05:06:07')
+test(db, 'timestamp', '2002-3-4 5:6:7', 'varchar', '2002-03-04 05:06:07')
+test(db, 'timestamp', '2002-3-4 5:6:7', 'date', DateTime.DateTime(2002,3,4))
+test(db, 'time', '5:6:7', 'text', '05:06:07')
+test(db, 'time', '5:6:7', 'varchar', '05:06:07')
diff --git a/tools/populate_cases_contacts.py b/tools/populate_cases_contacts.py
new file mode 100644
index 0000000..c409089
--- /dev/null
+++ b/tools/populate_cases_contacts.py
@@ -0,0 +1,114 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+
+import sys, os, datetime
+from random import Random
+
+sys.exit('''\
+NOTE:
+This script is currently non-functional due to changes in the application
+internal API's. See populate_cases_random.py for hints on how to return
+this script to functionality.''')
+ddot = os.pardir
+sys.path.insert(0, os.path.abspath(os.path.join(sys.path[0],ddot,ddot,'tools')))
+import csv_load
+sys.path.insert(0, '/var/www/cgi-bin/collection')
+from casemgr import cases, credentials, syndrome, contact
+from casemgr.schema import schema
+
+
+def xform_date(date):
+    if date:
+        year, month, day = date[:4], date[4:6], date[6:]
+        return '%s/%s/%s' % (day, month, year)
+
+def makephone(r):
+    ph = str(r.randint(40000000,99999999))
+    return ph[0:4] + '-' + ph[4:]
+
+def xform_person(record, person,r):
+    person.surname = record.surname
+    person.given_names = record.given_name
+    person.dob = xform_date(record.date_of_birth)
+    person.sex = record.sex.upper()
+    person.street_address = (record.street_number + ' ' + 
+                             record.address_1.title()).strip()
+    person.locality = record.suburb.upper()
+    person.state = record.state.lower()
+    person.postcode = record.postcode
+    person.home_phone = makephone(r)
+    person.work_phone = makephone(r)
+
+def main(args):
+    r = Random()
+    db = schema.define_db()
+    cred = credentials.Credentials()
+    cred.set_user(db, 'andrewm')
+    cred.get_units(db)
+    cred.set_unit(db, 0)
+    cred.authenticate_user(db, 'andrewm')
+    synd = [s for s in syndrome.get_syndromes(db, cred) if s.name == 'SARS'][0]
+    for filename in args:
+        f = open(filename)
+        testrecs = []
+        for rec in csv_load.load(f):
+            testrecs.append(rec)
+        f.close()
+        testrecs_len = len(testrecs)
+        try:
+            count = 0
+            while count < testrecs_len:
+                record = testrecs[count]
+                count += 1
+                print count
+                case = cases.new_case(db, cred, synd)
+                d = str(datetime.datetime.now()).split()[0].split("-")
+                t = str(datetime.datetime.now()).split()[1].split(":")[:-1]
+                case.case_row.onset_datetime = d[2] + "/" + d[1] + "/" + d[0] + " " + t[0] + ":" + t[1]
+                xform_person(record, case.person_row,r)
+                case.update(db)
+                for i in range(Random().choice([0,1,2,3,4,5,6,7,8910,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25])):
+                    mycontacts = contact.Contacts(db,case)
+                    mycontact = mycontacts.new_contact(db)
+                    record = testrecs[count]
+                    count += 1
+                    xform_person(record,mycontact.person_row,r)
+                    d = str(datetime.datetime.now()).split()[0].split("-")
+                    t = str(datetime.datetime.now()).split()[1].split(":")[:-1]
+                    mycontact.contact_row.contact_date = d[2] + "/" + d[1] + "/" + d[0] + " " + t[0] + ":" + t[1]
+                    """
+                    fu = contact.Followup(case,mycontact,db.new_row('followup_summary'))
+                    fu.get_form_ui(db)
+                    fu.followup_row = db.new_row(fu.form_ui.table)
+                    fu.followup_row.contact_id = mycontact.contact_row.contact_id
+                    fu.followuo_row.contact_date = ':'.join(str(datetime.datetime.now() - datetime.timedelta(2)).split(':')[:-1])
+                    fu.summary.contact_id = mycontact.contact_row.contact_id
+                    # self.summary.summary = ', '.join(summary).capitalize()
+                    fu.summary.form_date = fu.followup_row.form_date
+                    fu.summary.db_update()
+                    fu.followup_row.followup_summary_id =fu.summary.followup_summary_id
+                    fu.followup_row.db_update()
+                    """
+                    mycontact.update()
+                db.commit()
+        finally:
+            f.close()
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/tools/populate_cases_random.py b/tools/populate_cases_random.py
new file mode 100644
index 0000000..f92b4f1
--- /dev/null
+++ b/tools/populate_cases_random.py
@@ -0,0 +1,78 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+import sys, os
+import getpass
+import random
+
+sys.path.insert(0, os.path.abspath(os.path.join(sys.path[0],os.pardir)))
+sys.path.insert(0, os.getcwd())
+try:
+    from casemgr import globals
+except IOError, e:
+    sys.exit('%s\nThis script must be run from the application cgi-bin directory' % e)
+from casemgr import cases, credentials, syndrome
+import config
+
+def xform_date(date):
+    if date:
+        year, month, day = date[:4], date[4:6], date[6:]
+        return '%s/%s/%s' % (day, month, year)
+
+words=[w.strip() for w in open('/usr/share/dict/words')]
+
+def random_phone():
+    num = ''.join([random.choice('0123456789') for c in range(8)])
+    return num[:4] + '-' + num[4:]
+
+def random_person(person):
+    person.surname = random.choice(words).capitalize()
+    person.given_names = ' '.join([random.choice(words).capitalize()
+                                   for i in range(random.randint(1,2))])
+    person.dob = '%s/%s/%s' % (random.randint(1,28),
+                               random.randint(1,12),
+                               random.randint(1920,2003))
+                    
+    person.sex = random.choice(['M', 'F'])
+    person.street_address = '%s %s %s' % (random.randint(1,100),
+                                          random.choice(words).capitalize(),
+                                          random.choice(['St', 'Lane', 'Rd', 'Close', 'Ave']))
+    person.locality = random.choice(words).capitalize()
+    person.home_phone = random_phone()
+    person.work_phone = random_phone()
+    person.state = random.choice(['NSW', 'NSW', 'NSW', 'NSW', 'NSW', 'NSW', 'Vic', 'QLD', 'ACT', 'WA', 'NT', 'SA'])
+
+    person.postcode = '%s' % random.randint(2000, 2999)
+
+def main(args):
+    user = raw_input('%s Username: ' % (config.apptitle))
+    passwd = getpass.getpass()
+    cred = credentials.Credentials()
+    cred.authenticate_user(globals.db, user, passwd)
+    synd = [s for s in syndrome.syndromes if s.name == 'SARS'][0]
+    for record in xrange(int(args[0])):
+        case = cases.new_case(cred, synd.syndrome_id, None)
+        random_person(case.person)
+        case.update()
+        globals.db.commit()
+
+if __name__ == '__main__':
+    ourname = os.path.basename(sys.argv[0])
+    if len(sys.argv) != 2:
+        sys.exit('Usage: %s <case_count>' % ourname)
+    main(sys.argv[1:])
+
diff --git a/tools/populate_tasks.py b/tools/populate_tasks.py
new file mode 100644
index 0000000..8881ccb
--- /dev/null
+++ b/tools/populate_tasks.py
@@ -0,0 +1,108 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+from time import time
+import random
+from pyPgSQL import PgSQL
+
+def wq_rows(unit_ids, user_ids):
+    st = time()
+    rows = [(u, None) for u in unit_ids]
+    rows += [(None, u) for u in user_ids]
+    rows += [(None, None) for n in xrange(len(unit_ids) / 2)]
+    rows = [('Q %d' % n, 'X') + r for n, r in enumerate(rows)]
+    random.shuffle(rows)
+    print 'workqueues %d (%.1f)' % (len(rows), time() - st)
+    return rows
+
+def wqm_rows(shared_queues, unit_ids, user_ids):
+    st = time()
+    rows = []
+    idcount = len(unit_ids) + len(user_ids)
+    thresh = len(unit_ids)
+    for queue_id in shared_queues:
+        users = set()
+        units = set()
+        for n in xrange(random.randrange(3, 50)):
+            if random.randrange(idcount) < thresh:
+                while 1:
+                    id = random.choice(unit_ids)
+                    if id not in units:
+                        break
+                units.add(id)
+                rows.append((queue_id, id, None))
+            else:
+                while 1:
+                    id = random.choice(user_ids)
+                    if id not in users:
+                        break
+                users.add(id)
+                rows.append((queue_id, None, id))
+    random.shuffle(rows)
+    print 'wq members %d (%.1f)' % (len(rows), time() - st)
+    return rows
+
+def task_rows(case_ids, queue_ids):
+    st = time()
+    rows = []
+    c = 0
+    for queue_id in queue_ids:
+        case_id = random.choice(case_ids)
+        for n in xrange(random.randrange(1, 30)):
+            rows.append((queue_id, 'T %d %d' % (c, n), case_id))
+            c += 1
+    random.shuffle(rows)
+    print 'tasks %d (%.1f)' % (len(rows), time() - st)
+    return rows
+
+def fetchids(curs):
+    return [r[0] for r in curs.fetchall()]
+
+def many(curs, name, cmd, arg):
+    st = time()
+    curs.executemany(cmd, arg)
+    print '%s %d (%.1f)' % (name, len(arg), time() - st)
+
+def main():
+    db = PgSQL.connect(database='casemgr')
+    curs = db.cursor()
+    curs.execute('create index wqdesc on workqueues (description);')
+    curs.execute('select unit_id from units where unit_id not in (select unit_id from workqueues where unit_id is not null)')
+    unit_ids = fetchids(curs)
+    curs.execute('select user_id from users where user_id not in (select user_id from workqueues where user_id is not null)')
+    user_ids = fetchids(curs)
+    print 'Units %d, Users %d' % (len(unit_ids), len(user_ids))
+    # Create workqueues
+    many(curs, 'insert wq', 'insert into workqueues (name,description, unit_id, user_id) values (%s,%s,%s,%s)', wq_rows(unit_ids, user_ids))
+    # Find shared queues
+    curs.execute("select queue_id from workqueues where unit_id is null and user_id is null and description = 'X'")
+    shared_queues = fetchids(curs)
+    # Add members to shared queues
+    print 'Shared queues %s' % len(shared_queues)
+    many(curs, 'insert wqm', 'insert into workqueue_members (queue_id, unit_id, user_id) values (%s,%s,%s)', wqm_rows(shared_queues, unit_ids, user_ids))
+    # Create tasks
+    curs.execute("select master_id from cases")
+    case_ids = fetchids(curs)
+    curs.execute("select queue_id from workqueues where description='X'")
+    queue_ids = fetchids(curs)
+    many(curs, 'insert tasks', 'insert into tasks (queue_id, task_description, case_id) values (%s, %s, %s)', task_rows(case_ids, queue_ids))
+    curs.execute('drop index wqdesc;')
+    db.commit()
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/quarantine_export.py b/tools/quarantine_export.py
new file mode 100644
index 0000000..47cc354
--- /dev/null
+++ b/tools/quarantine_export.py
@@ -0,0 +1,183 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Quick and dirty script to extract persons currently in quarantine
+in NSW NetEpi Collection during swine flu response of 2009
+"""
+
+import sys
+import csv
+import ocpgdb
+from mx import DateTime
+
+order_by = 'surname', 'given_names', 'id'
+
+now = DateTime.now()
+
+
+def tablename(db, formname):
+    curs = db.cursor()
+    curs.execute('select cur_version from forms where label = %s', [formname])
+    row = curs.fetchone()
+    assert row is not None
+    return 'form_%s_%05d' % (formname, row[0])
+
+
+def age(dob):
+    if not dob:
+        return ''
+    age = (now - dob).days
+    if not age or age < 0:
+        return ''
+    if round(age) == 1:
+        return '%.0f day' % age
+    if age < 10:
+        return '%.0f days' % age
+    if age < 90:
+        return '%.0f weeks' % (age / 7)
+    years = age / 365.25
+    if years < 1:
+        return '%.0f months' % (age / 30.5)
+    if years < 2:
+        return '%.0f year' % years
+    return '%.0f years' % years
+
+
+def duration(v):
+    if not v:
+        return ''
+    days = (now - v).days
+    if days < 0:
+        return ''
+    return '%.0f days' % days
+
+
+class Record:
+
+    cols = (
+        'id', 'surname', 'given_names', 'dob', 'age', 'sex', 'interpreter_req', 
+        'start_date', 'duration',
+        'home_phone', 'work_phone', 'mobile_phone',
+        'e_mail', 
+        'street_address', 'locality', 'state', 'postcode', 'country',
+        'alt_street_address', 'alt_locality', 'alt_state', 'alt_postcode', 'alt_country',
+        'passport_number', 'passport_country',
+        'passport_number_2', 'passport_country_2',
+    )
+
+    def __init__(self, cols, row):
+        for col, value in zip(cols, row):
+            if isinstance(value, str):
+                value = value.strip()
+            setattr(self, col, value)
+        self.age = age(self.dob)
+        self.duration = duration(self.start_date)
+        if self.dob_is_approx:
+            self.dob = ''
+        for col in ('dob', 'start_date'):
+            value = getattr(self, col)
+            if value:
+                setattr(self, col, value.strftime('%Y-%m-%d'))
+
+    def values(self):
+        return [getattr(self, col) for col in self.cols]
+
+
+class Query:
+
+    demog_cols = (
+        'case_id', 'surname', 'given_names', 'dob', 'dob_is_approx',
+        'sex', 'interpreter_req', 
+        'home_phone', 'work_phone', 'mobile_phone',
+        'e_mail', 
+        'street_address', 'locality', 'state', 'postcode', 'country',
+        'alt_street_address', 'alt_locality', 'alt_state', 'alt_postcode', 'alt_country',
+        'passport_number', 'passport_country',
+        'passport_number_2', 'passport_country_2',
+    )
+
+    def query(self, db):
+        table = tablename(db, self.form)
+        cols = ','.join(('person_id', self.start_col) + self.demog_cols)
+        colmap = {'case_id': 'id', self.start_col: 'start_date'}
+        query = '''\
+select %s
+    from persons
+    join cases using (person_id)
+    join case_form_summary using (case_id)
+    join %s using (summary_id)
+    where not deleted and %s = 'True' and %s is null
+    ''' % (cols, table, self.in_quar_col, self.finished_col)
+        curs = db.cursor()
+        curs.execute(query)
+        cols = [colmap.get(d[0], d[0]) for d in curs.description]
+        while True:
+            rows = curs.fetchmany(200)
+            if not rows:
+                break
+            for row in rows:
+                yield Record(cols, row)
+
+
+class CaseQuery(Query):
+    form = 'swineflu'
+    in_quar_col = 'home_isolation'
+    start_col = 'home_isolation_start'
+    finished_col = 'home_isolation_finished'
+
+
+class ContactQuery(Query):
+    form = 'sf_contact'
+    in_quar_col = 'quarantined'
+    start_col = 'quarantine_start'
+    finished_col = 'quarantine_finished'
+
+
+class Records(dict):
+
+    def add(self, record):
+        self[record.person_id] = record
+
+    def sorted(self):
+        dsu = [([getattr(r, c) for c in order_by], r) for r in self.itervalues()]
+        dsu.sort()
+        return [p[1] for p in dsu]
+
+
+def main(dbname):
+    db = ocpgdb.connect(database=dbname, use_mx_datetime=True)
+    persons = Records()
+    for row in ContactQuery().query(db):
+        persons.add(row)
+    for row in CaseQuery().query(db):
+        persons.add(row)
+
+    writer = csv.writer(sys.stdout)
+    writer.writerow(Record.cols)
+    for record in persons.sorted():
+        writer.writerow(record.values())
+
+
+def usage():
+    sys.exit('Usage: %s <database>' % sys.argv[0])
+
+if __name__ == '__main__':
+    if len(sys.argv) != 2:
+        usage()
+    main(*sys.argv[1:])
diff --git a/tools/seed/data/case_status b/tools/seed/data/case_status
new file mode 100644
index 0000000..2148069
--- /dev/null
+++ b/tools/seed/data/case_status
@@ -0,0 +1,4 @@
+1|Preliminary
+2|Notified to DOH
+3|Notified to Federal DOH
+4|Deleted
diff --git a/tools/seed/data/forms b/tools/seed/data/forms
new file mode 100644
index 0000000..e69de29
diff --git a/tools/seed/data/group_syndromes b/tools/seed/data/group_syndromes
new file mode 100644
index 0000000..2355315
--- /dev/null
+++ b/tools/seed/data/group_syndromes
@@ -0,0 +1,4 @@
+1|1|2
+2|2|2
+3|1|3
+4|2|3
diff --git a/tools/seed/data/groups b/tools/seed/data/groups
new file mode 100644
index 0000000..9162ebf
--- /dev/null
+++ b/tools/seed/data/groups
@@ -0,0 +1,6 @@
+1|PHU|PHU|Public Health Units (RIGHTS)
+2|ED|ED|Emergency Departments (RIGHTS)
+3|GP|GP|General Practitioners (RIGHTS)
+4|LAB|LAB|Laboratories (RIGHTS)
+1000|NSHS|\N|Northern Sector Health Service
+1001|SESHS|\N|South Eastern Sector Health Service
diff --git a/tools/seed/data/syndrome_forms b/tools/seed/data/syndrome_forms
new file mode 100644
index 0000000..888ffe3
--- /dev/null
+++ b/tools/seed/data/syndrome_forms
@@ -0,0 +1,16 @@
+1|2|sars_symptoms
+2|2|sars_exposure
+3|2|sars_travel
+4|2|sars_china
+5|2|sars_hk
+7|2|hospital_admit
+8|2|lab_serology
+9|2|lab_culture
+10|2|lab_pcr
+11|2|lab_direct_ag
+12|2|lab_haem_biochem
+13|1|spox_case
+14|1|spox_exposure
+15|1|spox_history
+16|1|spox_laboratory
+17|2|sars_followup
diff --git a/tools/seed/data/syndrome_types b/tools/seed/data/syndrome_types
new file mode 100644
index 0000000..2eefd62
--- /dev/null
+++ b/tools/seed/data/syndrome_types
@@ -0,0 +1,2 @@
+1|Smallpox|An illness with acute onset of fever > 38.3 degrees C followed by a rash characterised by firm, deep seated vesicles or pustules in the same stage of development without other apparent cause.|True|\N|\N
+2|SARS|Fever greater than 38 degrees Celsius and cough, shortness of breath, difficulty breathing or hypoxia. Travel in last 10 days to an area with community transmission of SARS, or close contact in the last 10 days with a person known or suspected to have SARS infection.|True|\N|\N
diff --git a/tools/seed/data/unit_groups b/tools/seed/data/unit_groups
new file mode 100644
index 0000000..7004924
--- /dev/null
+++ b/tools/seed/data/unit_groups
@@ -0,0 +1,9 @@
+1|1|1000
+2|1|2
+3|2|1000
+4|2|1
+5|3|1001
+6|3|1
+7|4|1001
+8|4|4
+9|5|3
diff --git a/tools/seed/data/unit_users b/tools/seed/data/unit_users
new file mode 100644
index 0000000..0638d91
--- /dev/null
+++ b/tools/seed/data/unit_users
@@ -0,0 +1,8 @@
+2|0|2
+3|0|3
+4|1|4
+5|2|5
+6|3|6
+7|4|7
+8|5|8
+9|6|9
diff --git a/tools/seed/data/units b/tools/seed/data/units
new file mode 100644
index 0000000..c129dfa
--- /dev/null
+++ b/tools/seed/data/units
@@ -0,0 +1,6 @@
+1|t|St Elsewhere's Hospital Emergency Department|ED|St Elsewhere's Hospital, SOME SUBURB|SOME SUBURB|4
+2|t|Northern Sector Public Health Unit|PHU|St Elsewhere's Hospital, SOME SUBURB|SOME SUBURB|5
+3|t|South Eastern Sector Public Health Unit|PHU|South-Eastern Hospital, SOUTHEASTWICK|SOUTHESTWICK|6
+4|t|South Eastern Sector Laboratory Service|LAB|South-Eastern Hospital, SOUTHEASTWICK|SOUTHEASTWICK|7
+5|t|Suburban Medical Centre|GP|23 Somewhere St, SOME SUBURB|SOME SUBURB|8
+6|t|Airport Medical Centre|GP|Big Airport|CAPITAL CITY|9
diff --git a/tools/seed/data/users b/tools/seed/data/users
new file mode 100644
index 0000000..453cf0e
--- /dev/null
+++ b/tools/seed/data/users
@@ -0,0 +1,8 @@
+2|t|\N|epi_user|Epi user|An Epidemiology Branch user|485fd490027afe1fc1e71bb2e0e0b467|\N|\N|\N|\N|\N|\N|0|\N|\N
+3|t|\N|cdb_user|CDB user|A Communicable Diseases Branch user|b23db021b2b9ab68e9f40e9a2851c16d|\N|\N|\N|\N|\N|\N|0|\N|\N
+4|t|\N|hed_user|St Elsewhere's Hospital ED user|St Elsewhere's Hospital Emergency Department|145850d645713559d24907149eac27ae|\N|\N|\N|\N|\N|\N|0|\N|\N
+5|t|\N|nsphu_user|Northern Sector PHU user|Northern Sector Public Health Unit user|45c0c7f1c9ecb0ac2067a58c2326cb2e|\N|\N|\N|\N|\N|\N|0|\N|\N
+6|t|\N|sesphu_user|South-Eastern Sector PHU user|South-Eastern Sector Public Health Unit user|7942b023fc0c62107f77d8540198519e|\N|\N|\N|\N|\N|\N|0|\N|\N
+7|t|\N|sesls_user|South-Eastern Sector Laboratory Service user|South-Eastern Sector Laboratory Service user|d0a77f77af17a4ddd0e3cb46f52143b6|\N|\N|\N|\N|\N|\N|0|\N|\N
+8|t|\N|right|Dr A.L. Right|Dr A.L. Right, general practitioner|f0dcc49f6341ace2a3c92c7760e9150a|\N|\N|\N|\N|\N|\N|0|\N|\N
+9|t|\N|amc_user|Airport Medical Centre user|An Airport Medical Centre user|976e62d61e2fe2d175b2fb6878b6470f|\N|\N|\N|\N|\N|\N|0|\N|\N
diff --git a/tools/seed/seed_db b/tools/seed/seed_db
new file mode 100644
index 0000000..7fff97e
--- /dev/null
+++ b/tools/seed/seed_db
@@ -0,0 +1,48 @@
+if [ -z "$1" ]; then
+    echo "Usage: $0 <database>" >&2;
+    exit 1;
+fi
+dir=`dirname $0`/
+psql -f - -q $1 << EOF
+
+begin;
+\qecho === forms
+\copy forms from '${dir}data/forms' using delimiters '|'
+
+\qecho === syndrome_types
+\copy syndrome_types from '${dir}data/syndrome_types' using delimiters '|'
+SELECT setval('seq_syndrome_id_syndrome_types', 
+              (SELECT max(syndrome_id) FROM syndrome_types));
+
+\qecho === syndrome_forms
+\copy syndrome_forms from '${dir}data/syndrome_forms' using delimiters '|'
+SELECT setval('seq_syndrome_forms_id_syndrome_forms', 
+              (SELECT max(syndrome_forms_id) FROM syndrome_forms));
+
+\qecho === users
+\copy users from '${dir}data/users' using delimiters '|'
+SELECT setval('seq_user_id_users', (SELECT max(user_id) FROM users));
+
+\qecho === units
+\copy units from '${dir}data/units' using delimiters '|'
+SELECT setval('seq_unit_id_units', (SELECT max(unit_id) FROM units));
+
+\qecho === groups
+\copy groups from '${dir}data/groups' using delimiters '|'
+SELECT setval('seq_group_id_groups', (SELECT max(group_id) FROM groups));
+
+\qecho === unit_groups
+\copy unit_groups from '${dir}data/unit_groups' using delimiters '|'
+SELECT setval('seq_unit_groups_id_unit_groups', 
+              (SELECT max(unit_groups_id) FROM unit_groups));
+
+\qecho === unit_users
+\copy unit_users from '${dir}data/unit_users' using delimiters '|'
+SELECT setval('seq_unit_user_id_unit_users', 
+              (SELECT max(unit_user_id) FROM unit_users));
+
+\qecho === group_syndromes
+\copy group_syndromes from '${dir}data/group_syndromes' using delimiters '|'
+SELECT setval('seq_group_syndromes_id_group_syndromes', 
+              (SELECT max(group_syndromes_id) FROM group_syndromes));
+commit;
diff --git a/tools/userdump b/tools/userdump
new file mode 100755
index 0000000..211f519
--- /dev/null
+++ b/tools/userdump
@@ -0,0 +1,25 @@
+if [ -z "$1" ]; then
+    echo "Usage: $0 <database>" >&2;
+    exit 1;
+fi
+dir=userdata
+if [ ! -d "$dir" ]; then
+    mkdir "$dir"
+fi
+echo "Dumping into $dir"
+psql -f - -q $1 << EOF
+
+\qecho === groups
+\copy groups to '${dir}/groups' using delimiters '|'
+
+\qecho === units
+\copy units to '${dir}/units' using delimiters '|'
+
+\qecho === unit_groups
+\copy unit_groups to '${dir}/unit_groups' using delimiters '|'
+
+\qecho === users
+\copy users to '${dir}/users' using delimiters '|'
+
+\qecho === unit_users
+\copy unit_users to '${dir}/unit_users' using delimiters '|'
diff --git a/tools/userrestore b/tools/userrestore
new file mode 100755
index 0000000..07a6cd7
--- /dev/null
+++ b/tools/userrestore
@@ -0,0 +1,43 @@
+if [ -z "$1" ]; then
+    echo "Usage: $0 <database>" >&2;
+    exit 1;
+fi
+dir=userdata
+if [ ! -d "$dir" ]; then
+    echo "Data dir $dir does not exist?"
+    exit 1
+fi
+echo "Loading from $dir"
+psql -f - -q $1 << EOF
+
+\qecho === deleting existing user structures
+begin;
+delete from unit_groups where unit_id = 0 and group_id = 0;
+delete from unit_users where unit_id = 0 and user_id = 0;
+delete from groups where group_id = 0;
+delete from units where unit_id = 0;
+delete from users where user_id = 0;
+
+\qecho === groups
+\copy groups from '${dir}/groups' using delimiters '|'
+
+\qecho === users
+\copy users from '${dir}/users' using delimiters '|'
+
+\qecho === units
+\copy units from '${dir}/units' using delimiters '|'
+
+\qecho === unit_groups
+\copy unit_groups from '${dir}/unit_groups' using delimiters '|'
+
+\qecho === unit_users
+\copy unit_users from '${dir}/unit_users' using delimiters '|'
+
+SELECT setval('seq_user_id_users', (SELECT max(user_id) FROM users));
+SELECT setval('seq_unit_id_units', (SELECT max(unit_id) FROM units));
+SELECT setval('seq_group_id_groups', (SELECT max(group_id) FROM groups));
+SELECT setval('seq_unit_groups_id_unit_groups', 
+              (SELECT max(unit_groups_id) FROM unit_groups));
+SELECT setval('seq_unit_user_id_unit_users', 
+              (SELECT max(unit_user_id) FROM unit_users));
+commit;
diff --git a/tools/visdb.py b/tools/visdb.py
new file mode 100644
index 0000000..2dee348
--- /dev/null
+++ b/tools/visdb.py
@@ -0,0 +1,47 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+"""
+Generate a DOT (graphviz) file from the database describer
+"""
+
+from casemgr.schema import schema
+
+db = schema.define_db('::collection:')
+print 'digraph appname {'
+print 'pack=true;'
+print 'center=true;'
+#    print 'ratio=compress;'
+#    print 'size="11,8";'
+#    print 'rotate=90";'
+#    print 'model=resistance;'
+#    print 'overlap=false;'
+print 'overlap=scale;'
+print 'splines=true;'
+print 'nodesep=.1;'
+print 'epsilon=.1;'
+print 'mclimit=4;'
+print 'size="11.69x8.27";'
+print "node [shape=ellipse]"
+for table_desc in db.get_tables():
+    print '"%s";' % table_desc.name
+print 'edge [style=solid,weight=4]'
+for table_desc in db.get_tables():
+    for dep in table_desc.dependancies():
+        print '"%s" -> "%s";' % (table_desc.name, dep)
+print '};'
diff --git a/tools/xmltagfix.py b/tools/xmltagfix.py
new file mode 100644
index 0000000..f39dbe3
--- /dev/null
+++ b/tools/xmltagfix.py
@@ -0,0 +1,36 @@
+#
+#   The contents of this file are subject to the HACOS License Version 1.2
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  Software distributed under the License is distributed
+#   on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+#   implied. See the LICENSE file for the specific language governing
+#   rights and limitations under the License.  The Original Software
+#   is "NetEpi Collection". The Initial Developer of the Original
+#   Software is the Health Administration Corporation, incorporated in
+#   the State of New South Wales, Australia.
+#
+#   Copyright (C) 2004-2011 Health Administration Corporation, Australian
+#   Government Department of Health and Ageing, and others.
+#   All Rights Reserved.
+#
+#   Contributors: See the CONTRIBUTORS file for details of contributions.
+#
+
+import sys
+
+fixups = {
+    'givennames': 'given_names',
+    'address': 'street_address',
+    'suburb': 'locality',
+    'dob': 'DOB',
+}
+
+data = open(sys.argv[1]).read()
+for a, b in fixups.items():
+    data = data.replace('<%s>' % a, '<%s>' % b)
+    data = data.replace('</%s>' % a, '</%s>' % b)
+    data = data.replace('<%s/>' % a, '<%s/>' % b)
+f=open(sys.argv[1], 'w')
+f.write(data)
+f.close()
+    
diff --git a/wiki/__init__.py b/wiki/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wiki/api.py b/wiki/api.py
new file mode 100644
index 0000000..929e03a
--- /dev/null
+++ b/wiki/api.py
@@ -0,0 +1,217 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2005 Jonas Borgstr�m <jonas at edgewall.com>
+# Copyright (C) 2004-2005 Christopher Lenz <cmlenz at gmx.de>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+#         Christopher Lenz <cmlenz at gmx.de>
+
+from __future__ import generators
+try:
+    import threading
+except ImportError:
+    import dummy_threading as threading
+import time
+import urllib
+import re
+
+from wiki.core import *
+from wiki.util import to_utf8
+
+
+class IWikiChangeListener(Interface):
+    """Extension point interface for components that should get notified about
+    the creation, deletion and modification of wiki pages.
+    """
+
+    def wiki_page_added(page):
+        """Called whenever a new Wiki page is added."""
+
+    def wiki_page_changed(page, version, t, comment, author, ipnr):
+        """Called when a page has been modified."""
+
+    def wiki_page_deleted(page):
+        """Called when a page has been deleted."""
+
+
+class IWikiMacroProvider(Interface):
+    """Extension point interface for components that provide Wiki macros."""
+
+    def get_macros():
+        """Return an iterable that provides the names of the provided macros."""
+
+    def get_macro_description(name):
+        """Return a plain text description of the macro with the specified name.
+        """
+
+    def render_macro(req, name, content):
+        """Return the HTML output of the macro."""
+
+
+class IWikiSyntaxProvider(Interface):
+ 
+    def get_wiki_syntax():
+        """Return an iterable that provides additional wiki syntax."""
+ 
+    def get_link_resolvers():
+        """Return an iterable over (namespace, formatter) tuples."""
+ 
+
+class WikiSystem(Component):
+    """Represents the wiki system."""
+
+    implements(IWikiChangeListener, IWikiSyntaxProvider)
+
+    change_listeners = ExtensionPoint(IWikiChangeListener)
+    macro_providers = ExtensionPoint(IWikiMacroProvider)
+    syntax_providers = ExtensionPoint(IWikiSyntaxProvider)
+
+    INDEX_UPDATE_INTERVAL = 5 # seconds
+
+    def __init__(self):
+        self._index = None
+        self._last_index_update = 0
+        self._index_lock = threading.RLock()
+        self._compiled_rules = None
+        self._link_resolvers = None
+        self._helper_patterns = None
+        self._external_handlers = None
+
+    def _update_index(self):
+        self._index_lock.acquire()
+        try:
+            now = time.time()
+            if now > self._last_index_update + WikiSystem.INDEX_UPDATE_INTERVAL:
+                self.log.debug('Updating wiki page index')
+                db = self.env.get_db_cnx()
+                cursor = db.cursor()
+                cursor.execute("SELECT DISTINCT name FROM wiki")
+                self._index = {}
+                for (name,) in cursor:
+                    self._index[name] = True
+                self._last_index_update = now
+        finally:
+            self._index_lock.release()
+
+    # Public API
+
+    def get_pages(self, prefix=None):
+        """Iterate over the names of existing Wiki pages.
+
+        If the `prefix` parameter is given, only names that start with that
+        prefix are included.
+        """
+        self._update_index()
+        for page in self._index.keys():
+            if not prefix or page.startswith(prefix):
+                yield page
+
+    def has_page(self, pagename):
+        """Whether a page with the specified name exists."""
+        self._update_index()
+        return self._index.has_key(pagename)
+
+    def _get_rules(self):
+        self._prepare_rules()
+        return self._compiled_rules
+    rules = property(_get_rules)
+
+    def _get_helper_patterns(self):
+        self._prepare_rules()
+        return self._helper_patterns
+    helper_patterns = property(_get_helper_patterns)
+
+    def _get_external_handlers(self):
+        self._prepare_rules()
+        return self._external_handlers
+    external_handlers = property(_get_external_handlers)
+    
+    def _prepare_rules(self):
+        from formatter import Formatter
+        if not self._compiled_rules:
+            helpers = []
+            handlers = {}
+            syntax = Formatter._pre_rules[:]
+            i = 0
+            for resolver in self.syntax_providers:
+                for regexp, handler in resolver.get_wiki_syntax():
+                    handlers['i'+str(i)] = handler
+                    syntax.append('(?P<i%d>%s)' % (i, regexp))
+                    i += 1
+            syntax += Formatter._post_rules[:]
+            helper_re = re.compile(r'\?P<([a-z\d_]+)>')
+            for rule in syntax:
+                helpers += helper_re.findall(rule)[1:]
+            rules = re.compile('(?:' + '|'.join(syntax) + ')')
+            self._external_handlers = handlers
+            self._helper_patterns = helpers
+            self._compiled_rules = rules
+
+    def _get_link_resolvers(self):
+        if not self._link_resolvers:
+            resolvers = {}
+            for resolver in self.syntax_providers:
+                for namespace, handler in resolver.get_link_resolvers():
+                    resolvers[namespace] = handler
+            self._link_resolvers = resolvers
+        return self._link_resolvers
+    link_resolvers = property(_get_link_resolvers)
+
+    # IWikiChangeListener methods
+
+    def wiki_page_added(self, page):
+        if not self.has_page(page.name):
+            self.log.debug('Adding page %s to index' % page.name)
+            self._index[page.name] = True
+
+    def wiki_page_changed(self, page, version, t, comment, author, ipnr):
+        pass
+
+    def wiki_page_deleted(self, page):
+        if self.has_page(page.name):
+            self.log.debug('Removing page %s from index' % page.name)
+            del self._index[page.name]
+
+    # IWikiSyntaxProvider methods
+    
+    def get_wiki_syntax(self):
+        yield (r"\Z\A", lambda x, y, z: "")
+        return
+        ignore_missing = False # self.config.getbool('wiki', 'ignore_missing_pages')
+        yield (r"!?(?<!/)\b[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+"
+                "(?:#[A-Za-z0-9]+)?(?=\Z|\s|[.,;:!?\)}\]])",
+               lambda x, y, z: self._format_link(x, 'wiki', y, y,
+                                                 ignore_missing))
+
+    def get_link_resolvers(self):
+        yield ('wiki', self._format_fancy_link)
+
+    def _format_fancy_link(self, f, n, p, l):
+        return self._format_link(f, n, p, l, False)
+
+    def _format_link(self, formatter, ns, page, label, ignore_missing):
+        anchor = ''
+        if page.find('#') != -1:
+            anchor = page[page.find('#'):]
+            page = page[:page.find('#')]
+        page = urllib.unquote(page)
+        label = urllib.unquote(label)
+
+        if not self.has_page(page):
+            if ignore_missing:
+                return label
+            return '<a class="missing wiki" href="%s" rel="nofollow">%s?</a>' \
+                   % (formatter.href.wiki(page) + anchor, label)
+        else:
+            return '<a class="wiki" href="%s">%s</a>' \
+                   % (formatter.href.wiki(page) + anchor, label)
diff --git a/wiki/core.py b/wiki/core.py
new file mode 100644
index 0000000..058fdb6
--- /dev/null
+++ b/wiki/core.py
@@ -0,0 +1,209 @@
+#i -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2004 Jonas Borgstr�m <jonas at edgewall.com>
+# Copyright (C) 2004-2005 Christopher Lenz <cmlenz at gmx.de>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+#         Christopher Lenz <cmlenz at gmx.de>
+
+from wiki.util import TracError
+
+__all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface',
+           'TracError']
+
+
+class Interface(object):
+    """Dummy base class for interfaces.
+    
+    (Might use PyProtocols in the future.)
+    """
+
+class ExtensionPoint(object):
+    """Marker class for extension points in components."""
+
+    def __init__(self, interface):
+        """Create the extension point.
+        
+        @param interface: the `Interface` class that defines the protocol for
+                          the extension point
+        """
+        self.interface = interface
+
+    def __repr__(self):
+        """Return a textual representation of the extension point."""
+        return '<ExtensionPoint %s>' % self.interface.__name__
+
+
+class ComponentMeta(type):
+    """Meta class for components.
+    
+    Takes care of component and extension point registration.
+    """
+    _components = []
+    _registry = {}
+
+    def __new__(cls, name, bases, d):
+        """Create the component class."""
+        xtnpts = {}
+        for base in [base for base in bases
+                     if hasattr(base, '_extension_points')]:
+            xtnpts.update(base._extension_points)
+        for key, value in d.items():
+            if isinstance(value, ExtensionPoint):
+                xtnpts[key] = value
+                del d[key]
+
+        new_class = type.__new__(cls, name, bases, d)
+        new_class._extension_points = xtnpts
+
+        if name == 'Component':
+            # Don't put the Component base class in the registry
+            return new_class
+
+        # Only override __init__ for Components not inheriting ComponentManager
+        if True not in [issubclass(x, ComponentManager) for x in bases]:
+            # Allow components to have a no-argument initializer so that
+            # they don't need to worry about accepting the component manager
+            # as argument and invoking the super-class initializer
+            init = d.get('__init__')
+            if not init:
+                # Because we're replacing the initializer, we need to make sure
+                # that any inherited initializers are also called.
+                for init in [b.__init__._original for b in new_class.mro()
+                             if issubclass(b, Component)
+                             and b.__dict__.has_key('__init__')]:
+                    break
+            def maybe_init(self, compmgr, init=init, cls=new_class):
+                if not cls in compmgr.components:
+                    compmgr.components[cls] = self
+                    if init:
+                        init(self)
+            maybe_init._original = init
+            setattr(new_class, '__init__', maybe_init)
+
+        if d.get('abstract'):
+            # Don't put abstract component classes in the registry
+            return new_class
+
+        ComponentMeta._components.append(new_class)
+        for interface in d.get('_implements', []):
+            ComponentMeta._registry.setdefault(interface, []).append(new_class)
+        for base in [base for base in bases if hasattr(base, '_implements')]:
+            for interface in base._implements:
+                ComponentMeta._registry.setdefault(interface, []).append(new_class)
+
+        return new_class
+
+
+def implements(*interfaces):
+    """
+    Can be used in the class definiton of `Component` subclasses to declare
+    the extension points that are extended.
+    """
+    import sys
+
+    frame = sys._getframe(1)
+    locals = frame.f_locals
+
+    # Some sanity checks
+    assert locals is not frame.f_globals and '__module__' in frame.f_locals, \
+           'implements() can only be used in a class definition'
+    assert not '_implements' in locals, \
+           'implements() can only be used once in a class definition'
+
+    locals['_implements'] = interfaces
+
+
+class Component(object):
+    """Base class for components.
+
+    Every component can declare what extension points it provides, as well as
+    what extension points of other components it extends.
+    """
+    __metaclass__ = ComponentMeta
+
+    def __new__(cls, *args, **kwargs):
+        """Return an existing instance of the component if it has already been
+        activated, otherwise create a new instance.
+        """
+        # If this component is also the component manager, just invoke that
+        if issubclass(cls, ComponentManager):
+            self = super(Component, cls).__new__(cls)
+            self.compmgr = self
+            return self
+
+        # The normal case where the component is not also the component manager
+        compmgr = args[0]
+        if not cls in compmgr.components:
+            self = super(Component, cls).__new__(cls)
+            self.compmgr = compmgr
+            compmgr.component_activated(self)
+            return self
+        return compmgr[cls]
+
+    def __getattr__(self, name):
+        """If requesting an extension point member, return a list of components
+        that declare to implement the extension point interface."""
+        xtnpt = self._extension_points.get(name)
+        if xtnpt:
+            extensions = ComponentMeta._registry.get(xtnpt.interface, [])
+            return [self.compmgr[cls] for cls in extensions
+                    if self.compmgr[cls]]
+        cls = self.__class__.__name__
+        if hasattr(self, '__module__'):
+            cls = '.'.join((self.__module__, cls))
+        raise AttributeError, "'%s' object has no attribute '%s'" % (cls, name)
+
+
+class ComponentManager(object):
+    """The component manager keeps a pool of active components."""
+
+    def __init__(self):
+        """Initialize the component manager."""
+        self.components = {}
+        if isinstance(self, Component):
+            self.components[self.__class__] = self
+
+    def __contains__(self, cls):
+        """Return wether the given class is in the list of active components."""
+        return cls in self.components
+
+    def __getitem__(self, cls):
+        """Activate the component instance for the given class, or return the
+        existing the instance if the component has already been activated."""
+        component = self.components.get(cls)
+        if not component:
+            if not self.is_component_enabled(cls):
+                return None
+            if cls not in ComponentMeta._components:
+                raise TracError, 'Component "%s" not registered' % cls.__name__
+            try:
+                component = cls(self)
+            except TypeError, e:
+                raise TracError, 'Unable to instantiate component "%s" (%s)' \
+                                 % (cls.__name__, e)
+        return component
+
+    def component_activated(self, component):
+        """Can be overridden by sub-classes so that special initialization for
+        components can be provided.
+        """
+
+    def is_component_enabled(self, cls):
+        """Can be overridden by sub-classes to veto the activation of a
+        component.
+
+        If this method returns False, the component with the given class will
+        not be available.
+        """
+        return True
diff --git a/wiki/env.py b/wiki/env.py
new file mode 100644
index 0000000..91db925
--- /dev/null
+++ b/wiki/env.py
@@ -0,0 +1,137 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2005 Jonas Borgstr�m <jonas at edgewall.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+
+from __future__ import generators
+
+import os
+from ConfigParser import SafeConfigParser
+
+from wiki.core import Component, ComponentManager, implements, Interface, \
+                      ExtensionPoint, TracError
+
+__all__ = ['Environment']
+
+
+class Environment(Component, ComponentManager):
+    """An environment is a trac framework wrapper for a context object """   
+    def __init__(self, ctx):
+        """Initialize the environment.
+        
+        @param ctx:    the context object
+        """
+        ComponentManager.__init__(self)
+
+        self.ctx = ctx
+        #self.load_config()
+        #self.setup_log()
+
+        #from trac.loader import load_components
+        #load_components(self)
+
+    def _get_config(self):
+        # The context has everything in the config placed into it
+        return self.ctx.config
+    config = property(_get_config)
+
+    def component_activated(self, component):
+        """Initialize additional member variables for components.
+        
+        Every component activated through the `Environment` object gets three
+        member variables: `env` (the environment object), `config` (the
+        environment configuration) and `log` (a logger object)."""
+        component.env = self
+        component.config = self.config
+        #component.log = self.log
+
+    def is_component_enabled(self, cls):
+        """Implemented to only allow activation of components that are not
+        disabled in the configuration.
+        
+        This is called by the `ComponentManager` base class when a component is
+        about to be activated. If this method returns false, the component does
+        not get activated."""
+        if not isinstance(cls, (str, unicode)):
+            component_name = (cls.__module__ + '.' + cls.__name__).lower()
+        else:
+            component_name = cls.lower()
+
+        rules = [(name.lower(), value.lower() in ('enabled', 'on'))
+                 for name, value in self.config.options('components')]
+        rules.sort(lambda a, b: -cmp(len(a[0]), len(b[0])))
+
+        for pattern, enabled in rules:
+            if component_name == pattern or pattern.endswith('*') \
+                    and component_name.startswith(pattern[:-1]):
+                return enabled
+
+        # By default, all components in the trac package are enabled
+        return component_name.startswith('trac.')
+
+    def get_db_cnx(self):
+        """Return a database connection from the connection pool."""
+        raise NotImplementedError
+
+    def shutdown(self):
+        """Close the environment."""
+        pass
+
+    def get_version(self, db=None):
+        """Return the current version of the database."""
+        raise NotImplementedError
+
+    def load_config(self):
+        """Load the configuration file."""
+        self.config = SafeConfigParser()
+        #for section, name, value in db_default.default_config:
+        #    self.config.setdefault(section, name, value)
+
+    def get_templates_dir(self):
+        """Return absolute path to the templates directory."""
+        raise NotImplementedError
+
+    def get_htdocs_dir(self):
+        """Return absolute path to the htdocs directory."""
+        raise NotImplementedError
+
+    def get_log_dir(self):
+        """Return absolute path to the log directory."""
+        raise NotImplementedError
+
+    def setup_log(self):
+        """Initialize the logging sub-system."""
+        from log import logger_factory
+        logtype = 'stderr' # self.config.get('logging', 'log_type')
+        loglevel = 'WARNING' # self.config.get('logging', 'log_level')
+        logfile = '/tmp/log' # self.config.get('logging', 'log_file')
+        if not os.path.isabs(logfile):
+            logfile = os.path.join(self.get_log_dir(), logfile)
+        logid = self.config.appname # an ID
+        self.log = logger_factory(logtype, logfile, loglevel, logid)
+
+    def get_known_users(self, cnx=None):
+        """Generator that yields information about all known users, i.e. users
+        that have logged in to this Trac environment and possibly set their name
+        and email.
+
+        This function generates one tuple for every user, of the form
+        (username, name, email) ordered alpha-numerically by username.
+
+        @param cnx: the database connection; if ommitted, a new connection is
+                    retrieved
+        """
+        return os.path.join(self.path, 'log')
+
+
diff --git a/wiki/formatter.py b/wiki/formatter.py
new file mode 100644
index 0000000..ff00481
--- /dev/null
+++ b/wiki/formatter.py
@@ -0,0 +1,699 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2006 Edgewall Software
+# Copyright (C) 2003-2005 Jonas Borgstr�m <jonas at edgewall.com>
+# Copyright (C) 2004-2005 Christopher Lenz <cmlenz at gmx.de>
+# Copyright (C) 2005-2006 Christian Boos <cboos at neuf.fr>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+#         Christopher Lenz <cmlenz at gmx.de>
+
+from __future__ import generators
+import re
+import os
+import urllib
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+import wiki.util
+#from trac.mimeview import *
+from wiki.api import WikiSystem
+
+__all__ = ['wiki_to_html', 'wiki_to_oneliner']
+
+
+def system_message(msg, text):
+    return """<div class="system-message">
+ <strong>%s</strong>
+ <pre>%s</pre>
+</div>
+""" % (wiki.util.escape(msg), wiki.util.escape(text))
+
+
+class WikiProcessor(object):
+
+    def __init__(self, env, name):
+        self.env = env
+        self.name = name
+        self.error = None
+
+        # Disable the HTML processor for now (ticket #255)
+        builtin_processors = { # 'html': self._html_processor,
+                              'default': self._default_processor,
+                              'comment': self._comment_processor}
+        self.processor = builtin_processors.get(name)
+        if not self.processor:
+            # Find a matching wiki macro
+            wiki = WikiSystem(self.env)
+            for macro_provider in wiki.macro_providers:
+                if self.name in list(macro_provider.get_macros()):
+                    self.processor = self._macro_processor
+                    break
+        if not self.processor:
+            # For now, we have no extensible plugin architecture (tickets #254 and #255)
+            self.processor = self._default_processor
+            self.error = 'No macro named [[%s]] found' % name
+            ## # Find a matching mimeview renderer
+            ## from trac.mimeview.api import MIME_MAP
+            ## if MIME_MAP.has_key(self.name):
+            ##     self.name = MIME_MAP[self.name]
+            ##     self.processor = self._mimeview_processor
+            ## elif self.name in MIME_MAP.values():
+            ##     self.processor = self._mimeview_processor
+            ## else:
+            ##     self.processor = self._default_processor
+            ##     self.error = 'No macro named [[%s]] found' % name
+
+    def _comment_processor(self, req, text):
+        return ''
+
+    def _default_processor(self, req, text):
+        return '<pre class="wiki">' + wiki.util.escape(text) + '</pre>\n'
+
+    def _html_processor(self, req, text):
+        from HTMLParser import HTMLParseError
+        try:
+            return wiki.util.Markup(text).sanitize()
+        except HTMLParseError, e:
+            self.env.log.warn(e)
+            return system_message('HTML parsing error: %s' % wiki.util.escape(e.msg),
+                                  text.splitlines()[e.lineno - 1].strip())
+
+    def _macro_processor(self, req, text):
+        wiki = WikiSystem(self.env)
+        for macro_provider in wiki.macro_providers:
+            if self.name in list(macro_provider.get_macros()):
+                self.env.log.debug('Executing Wiki macro %s by provider %s'
+                                   % (self.name, macro_provider))
+                return macro_provider.render_macro(req, self.name, text)
+
+    def _mimeview_processor(self, req, text):
+        return Mimeview(self.env).render(req, self.name, text)
+
+    def process(self, req, text, inline=False):
+        if self.error:
+            return system_message(wiki.util.Markup('Error: Failed to load processor '
+                                              '<code>%s</code>', self.name),
+                                  self.error)
+        text = self.processor(req, text)
+        if inline:
+            code_block_start = re.compile('^<div class="code-block">')
+            code_block_end = re.compile('</div>$')
+            text, nr = code_block_start.subn('<span class="code-block">', text, 1 )
+            if nr:
+                text, nr = code_block_end.subn('</span>', text, 1 )
+            return text
+        else:
+            return text
+
+
+class Formatter(object):
+    flavor = 'default'
+
+    # Some constants used for clarifying the Wiki regexps:
+
+    BOLDITALIC_TOKEN = r"\*\*"
+    BOLD_TOKEN = r"\*"
+    ITALIC_TOKEN = "_"
+    UNDERLINE_TOKEN = "__"
+    STRIKE_TOKEN = "~~"
+    SUBSCRIPT_TOKEN = ",,"
+    SUPERSCRIPT_TOKEN = r"\^"
+    INLINE_TOKEN = "`"
+
+    LINK_SCHEME = r"(?:https?)" # r"[\w.+-]+" # as per RFC 2396
+
+    QUOTED_STRING = r"'[^']+'|\"[^\"]+\""
+
+    SHREF_TARGET_FIRST = r"[\w/?!#@]"
+    SHREF_TARGET_MIDDLE = r"(?:\|(?=[^|\s])|[^|<>\s])"
+    SHREF_TARGET_LAST = r"[a-zA-Z0-9/=]" # we don't want "_"
+
+    LHREF_RELATIVE_TARGET = r"[/.][^\s[\]]*"
+
+
+    # Rules provided by IWikiSyntaxProviders will be inserted,
+    # between _pre_rules and _post_rules
+
+    _pre_rules = [
+        # Font styles
+        r"(?P<bolditalic>%s)" % BOLDITALIC_TOKEN,
+        r"(?P<bold>%s)" % BOLD_TOKEN,
+        r"(?P<underline>!?%s)" % UNDERLINE_TOKEN,
+        r"(?P<italic>%s)" % ITALIC_TOKEN,
+        r"(?P<strike>!?%s)" % STRIKE_TOKEN,
+        r"(?P<subscript>!?%s)" % SUBSCRIPT_TOKEN,
+        r"(?P<superscript>!?%s)" % SUPERSCRIPT_TOKEN,
+        r"(?P<inlinecode>!?\{\{\{(?P<inline>.*?)\}\}\})",
+        r"(?P<inlinecode2>!?%s(?P<inline2>.*?)%s)" \
+        % (INLINE_TOKEN, INLINE_TOKEN)]
+
+    _post_rules = [
+        r"(?P<htmlescape>[&<>])",
+        # shref corresponds to short TracLinks, i.e. sns:stgt
+        r"(?P<shref>!?((?P<sns>%s):(?P<stgt>%s|%s(?:%s*%s)?)))" \
+        % (LINK_SCHEME, QUOTED_STRING,
+           SHREF_TARGET_FIRST, SHREF_TARGET_MIDDLE, SHREF_TARGET_LAST),
+        # lhref corresponds to long TracLinks, i.e. [lns:ltgt label?]
+        r"(?P<lhref>!?\[(?:(?P<lns>%s):(?P<ltgt>%s|[^\]\s]*)|(?P<rel>%s))"
+        r"(?:\s+(?P<label>%s|[^\]]+))?\])" \
+        % (LINK_SCHEME, QUOTED_STRING, LHREF_RELATIVE_TARGET, QUOTED_STRING),
+#       # macro call
+#       (r"(?P<macro>!?\[\[(?P<macroname>[\w/+-]+)"
+#        r"(\]\]|\((?P<macroargs>.*?)\)\]\]))"),
+        # heading, list, definition, indent, table...
+        r"(?P<heading>^\s*(?P<hdepth>=+)\s.*\s(?P=hdepth)\s*$)",
+        r"(?P<list>^(?P<ldepth>\s+)(?:\*|\d+\.) )",
+        r"(?P<definition>^\s+(.+)::)\s*",
+        r"(?P<indent>^(?P<idepth>\s+)(?=\S))",
+        r"(?P<last_table_cell>\|\|\s*$)",
+        r"(?P<table_cell>\|\|)"]
+
+    _processor_re = re.compile('#\!([\w+-][\w+-/]*)')
+    _anchor_re = re.compile('[^\w\d\.-:]+', re.UNICODE)
+    
+    img_re = re.compile(r"\.(gif|jpg|jpeg|png)(\?.*)?$", re.IGNORECASE)
+
+    def __init__(self, env, req=None, absurls=False):
+        self.env = env
+        self.req = req
+        self._absurls = absurls
+        self._anchors = []
+        self._open_tags = []
+        self.href = absurls and env.abs_href or env.href
+        self._local = None # env.abs_href.base # env.config.get('project', 'url') or env.abs_href.base
+
+    def _get_db(self):
+        return self.env.ctx.get_db()
+    db = property(fget=_get_db)
+
+    def _get_rules(self):
+        return WikiSystem(self.env).rules
+    rules = property(_get_rules)
+
+    def _get_link_resolvers(self):
+        return WikiSystem(self.env).link_resolvers
+    link_resolvers = property(_get_link_resolvers)
+
+    def replace(self, fullmatch):
+        wiki = WikiSystem(self.env)        
+        for itype, match in fullmatch.groupdict().items():
+            if match and not itype in wiki.helper_patterns:
+                # Check for preceding escape character '!'
+                if match[0] == '!':
+                    return match[1:]
+                if itype in wiki.external_handlers:
+                    return wiki.external_handlers[itype](self, match, fullmatch)
+                else:
+                    return getattr(self, '_' + itype + '_formatter')(match, fullmatch)
+
+    def tag_open_p(self, tag):
+        """Do we currently have any open tag with @tag as end-tag"""
+        return tag in self._open_tags
+
+    def close_tag(self, tag):
+        tmp =  ''
+        for i in xrange(len(self._open_tags)-1, -1, -1):
+            tmp += self._open_tags[i][1]
+            if self._open_tags[i][1] == tag:
+                del self._open_tags[i]
+                for j in xrange(i, len(self._open_tags)):
+                    tmp += self._open_tags[j][0]
+                break
+        return tmp
+
+    def open_tag(self, open, close):
+        self._open_tags.append((open, close))
+
+    def simple_tag_handler(self, open_tag, close_tag):
+        """Generic handler for simple binary style tags"""
+        if self.tag_open_p((open_tag, close_tag)):
+            return self.close_tag(close_tag)
+        else:
+            self.open_tag(open_tag, close_tag)
+        return open_tag
+
+    def _bolditalic_formatter(self, match, fullmatch):
+        italic = ('<i>', '</i>')
+        italic_open = self.tag_open_p(italic)
+        tmp = ''
+        if italic_open:
+            tmp += italic[1]
+            self.close_tag(italic[1])
+        tmp += self._bold_formatter(match, fullmatch)
+        if not italic_open:
+            tmp += italic[0]
+            self.open_tag(*italic)
+        return tmp
+
+    def _unquote(self, text):
+        if text and text[0] in "'\"" and text[0] == text[-1]:
+            return text[1:-1]
+        else:
+            return text
+
+    def _shref_formatter(self, match, fullmatch):
+        ns = fullmatch.group('sns')
+        target = self._unquote(fullmatch.group('stgt'))
+        return self._make_link(ns, target, match, match)
+
+    def _lhref_formatter(self, match, fullmatch):
+        ns = fullmatch.group('lns')
+        target = self._unquote(fullmatch.group('ltgt'))
+        label = fullmatch.group('label')
+        if not label: # e.g. `[http://target]` or `[wiki:target]`
+            if target:
+                if target.startswith('//'): # for `[http://target]`
+                    label = ns+':'+target   # use `http://target`
+                else:                       # for `wiki:target`
+                    label = target          # use only `target`
+            else: # e.g. `[search:]` 
+                label = ns
+        label = self._unquote(label)
+        rel = fullmatch.group('rel')
+        if rel:
+            return self._make_relative_link(rel, label or rel)
+        else:
+            return self._make_link(ns, target, match, label)
+
+    def _make_link(self, ns, target, match, label):
+        if ns in self.link_resolvers:
+            return self.link_resolvers[ns](self, ns, target,
+                                           wiki.util.escape(label, False))
+        elif target.startswith('//') or ns == "mailto":
+            return self._make_ext_link(ns+':'+target, label)
+        else:
+            return wiki.util.escape(match)
+
+    def _make_ext_link(self, url, text, title=''):
+        url = wiki.util.escape(url)
+        text, title = wiki.util.escape(text), wiki.util.escape(title)
+        title_attr = title and ' title="%s"' % title or ''
+        if Formatter.img_re.search(url) and self.flavor != 'oneliner':
+            return '<img src="%s" alt="%s" />' % (url, title or text)
+        return '<a target="_blank" href="%s"%s>%s</a>' % (url, title_attr, text)
+
+    def _make_relative_link(self, url, text):
+        url, text = wiki.util.escape(url), wiki.util.escape(text)
+        if Formatter.img_re.search(url) and self.flavor != 'oneliner':
+            return '<img src="%s" alt="%s" />' % (url, text)
+        return '<a target="_blank" href="%s">%s</a>' % (url, text)
+
+    def _bold_formatter(self, match, fullmatch):
+        return self.simple_tag_handler('<strong>', '</strong>')
+
+    def _italic_formatter(self, match, fullmatch):
+        return self.simple_tag_handler('<i>', '</i>')
+
+    def _underline_formatter(self, match, fullmatch):
+        if match[0] == '!':
+            return match[1:]
+        else:
+            return self.simple_tag_handler('<span class="underline">',
+                                           '</span>')
+
+    def _strike_formatter(self, match, fullmatch):
+        if match[0] == '!':
+            return match[1:]
+        else:
+            return self.simple_tag_handler('<del>', '</del>')
+
+    def _subscript_formatter(self, match, fullmatch):
+        if match[0] == '!':
+            return match[1:]
+        else:
+            return self.simple_tag_handler('<sub>', '</sub>')
+
+    def _superscript_formatter(self, match, fullmatch):
+        if match[0] == '!':
+            return match[1:]
+        else:
+            return self.simple_tag_handler('<sup>', '</sup>')
+
+    def _inlinecode_formatter(self, match, fullmatch):
+        return '<tt>%s</tt>' % wiki.util.escape(fullmatch.group('inline'))
+
+    def _inlinecode2_formatter(self, match, fullmatch):
+        return '<tt>%s</tt>' % wiki.util.escape(fullmatch.group('inline2'))
+
+    def _htmlescape_formatter(self, match, fullmatch):
+        return match == "&" and "&" or match == "<" and "<" or ">"
+
+    def _macro_formatter(self, match, fullmatch):
+        name = fullmatch.group('macroname')
+        if name in ['br', 'BR']:
+            return '<br />'
+        args = fullmatch.group('macroargs')
+        try:
+            macro = WikiProcessor(self.env, name)
+            return macro.process(self.req, args, 1)
+        except Exception, e:
+            self.env.log.error('Macro %s(%s) failed' % (name, args),
+                               exc_info=True)
+            return system_message('Error: Macro %s(%s) failed' % (name, args), e)
+
+    def _heading_formatter(self, match, fullmatch):
+        match = match.strip()
+        self.close_table()
+        self.close_paragraph()
+        self.close_indentation()
+        self.close_list()
+        self.close_def_list()
+
+        depth = min(len(fullmatch.group('hdepth')), 5)
+        heading = match[depth + 1:len(match) - depth - 1]
+
+        text = wiki_to_oneliner(heading, self.env, absurls=self._absurls)
+        sans_markup = re.sub(r'</?\w+(?: .*?)?>', '', text)
+
+        anchor = self._anchor_re.sub('', sans_markup.decode('utf-8'))
+        if not anchor or not anchor[0].isalpha():
+            # an ID must start with a letter in HTML
+            anchor = 'a' + anchor
+        i = 1
+        anchor = anchor_base = anchor.encode('utf-8')
+        while anchor in self._anchors:
+            anchor = anchor_base + str(i)
+            i += 1
+        self._anchors.append(anchor)
+        self.out.write('<h%d id="%s">%s</h%d>' % (depth, anchor, text, depth))
+
+    def _indent_formatter(self, match, fullmatch):
+        depth = int((len(fullmatch.group('idepth')) + 1) / 2)
+        list_depth = len(self._list_stack)
+        if list_depth > 0 and depth == list_depth + 1:
+            self.in_list_item = 1
+        else:
+            self.open_indentation(depth)
+        return ''
+
+    def _last_table_cell_formatter(self, match, fullmatch):
+        return ''
+
+    def _table_cell_formatter(self, match, fullmatch):
+        self.open_table()
+        self.open_table_row()
+        if self.in_table_cell:
+            return '</td><td>'
+        else:
+            self.in_table_cell = 1
+            return '<td>'
+
+    def close_indentation(self):
+        self.out.write(('</blockquote>' + os.linesep) * self.indent_level)
+        self.indent_level = 0
+
+    def open_indentation(self, depth):
+        if self.in_def_list:
+            return
+        diff = depth - self.indent_level
+        if diff != 0:
+            self.close_paragraph()
+            self.close_indentation()
+            self.close_list()
+            self.indent_level = depth
+            self.out.write(('<blockquote>' + os.linesep) * depth)
+
+    def _list_formatter(self, match, fullmatch):
+        ldepth = len(fullmatch.group('ldepth'))
+        depth = int((len(fullmatch.group('ldepth')) + 1) / 2)
+        self.in_list_item = depth > 0
+        type_ = ['ol', 'ul'][match[ldepth] == '*']
+        self._set_list_depth(depth, type_)
+        return ''
+
+    def _definition_formatter(self, match, fullmatch):
+        tmp = self.in_def_list and '</dd>' or '<dl>'
+        tmp += '<dt>%s</dt><dd>' % wiki_to_oneliner(match[:-2], self.env)
+        self.in_def_list = True
+        return tmp
+
+    def close_def_list(self):
+        if self.in_def_list:
+            self.out.write('</dd></dl>\n')
+        self.in_def_list = False
+
+    def _set_list_depth(self, depth, type_):
+        current_depth = len(self._list_stack)
+        diff = depth - current_depth
+        self.close_table()
+        self.close_paragraph()
+        self.close_indentation()
+        if diff > 0:
+            for i in range(diff):
+                self._list_stack.append(type_)
+                self.out.write('<%s><li>' % type_)
+        elif diff < 0:
+            for i in range(-diff):
+                tmp = self._list_stack.pop()
+                self.out.write('</li></%s>' % tmp)
+            if self._list_stack != [] and type_ != self._list_stack[-1]:
+                tmp = self._list_stack.pop()
+                self._list_stack.append(type_)
+                self.out.write('</li></%s><%s><li>' % (tmp, type_))
+            if depth > 0:
+                self.out.write('</li><li>')
+        # diff == 0
+        elif self._list_stack != [] and type_ != self._list_stack[-1]:
+            tmp = self._list_stack.pop()
+            self._list_stack.append(type_)
+            self.out.write('</li></%s><%s><li>' % (tmp, type_))
+        elif depth > 0:
+            self.out.write('</li><li>')
+
+    def close_list(self):
+        if self._list_stack != []:
+            self._set_list_depth(0, None)
+
+    def open_paragraph(self):
+        if not self.paragraph_open:
+            self.out.write('<p>' + os.linesep)
+            self.paragraph_open = 1
+
+    def close_paragraph(self):
+        if self.paragraph_open:
+            while self._open_tags != []:
+                self.out.write(self._open_tags.pop()[1])
+            self.out.write('</p>' + os.linesep)
+            self.paragraph_open = 0
+
+    def open_table(self):
+        if not self.in_table:
+            self.close_paragraph()
+            self.close_indentation()
+            self.close_list()
+            self.close_def_list()
+            self.in_table = 1
+            self.out.write('<table class="wiki">' + os.linesep)
+
+    def open_table_row(self):
+        if not self.in_table_row:
+            self.open_table()
+            self.in_table_row = 1
+            self.out.write('<tr>')
+
+    def close_table_row(self):
+        if self.in_table_row:
+            self.in_table_row = 0
+            if self.in_table_cell:
+                self.in_table_cell = 0
+                self.out.write('</td>')
+
+            self.out.write('</tr>')
+
+    def close_table(self):
+        if self.in_table:
+            self.close_table_row()
+            self.out.write('</table>' + os.linesep)
+            self.in_table = 0
+
+    def handle_code_block(self, line):
+        if line.strip() == '{{{':
+            self.in_code_block += 1
+            if self.in_code_block == 1:
+                self.code_processor = None
+                self.code_text = ''
+            else:
+                self.code_text += line + os.linesep
+                if not self.code_processor:
+                    self.code_processor = WikiProcessor(self.env, 'default')
+        elif line.strip() == '}}}':
+            self.in_code_block -= 1
+            if self.in_code_block == 0 and self.code_processor:
+                self.close_paragraph()
+                self.close_table()
+                self.out.write(self.code_processor.process(self.req, self.code_text))
+            else:
+                self.code_text += line + os.linesep
+        elif not self.code_processor:
+            match = Formatter._processor_re.search(line)
+            if match:
+                name = match.group(1)
+                self.code_processor = WikiProcessor(self.env, name)
+            else:
+                self.code_text += line + os.linesep 
+                self.code_processor = WikiProcessor(self.env, 'default')
+        else:
+            self.code_text += line + os.linesep
+
+    def format(self, text, out, escape_newlines=False):
+        self.out = out
+        self._open_tags = []
+        self._list_stack = []
+
+        self.in_code_block = 0
+        self.in_table = 0
+        self.in_def_list = 0
+        self.in_table_row = 0
+        self.in_table_cell = 0
+        self.indent_level = 0
+        self.paragraph_open = 0
+
+        for line in text.splitlines():
+            # Handle code block
+            if self.in_code_block or line.strip() == '{{{':
+                self.handle_code_block(line)
+                continue
+            # Handle Horizontal ruler
+            elif line[0:4] == '----':
+                self.close_paragraph()
+                self.close_indentation()
+                self.close_list()
+                self.close_def_list()
+                self.close_table()
+                self.out.write('<hr />' + os.linesep)
+                continue
+            # Handle new paragraph
+            elif line == '':
+                self.close_paragraph()
+                self.close_indentation()
+                self.close_list()
+                self.close_def_list()
+                continue
+
+            if escape_newlines:
+                line += ' [[BR]]'
+            self.in_list_item = False
+            # Throw a bunch of regexps on the problem
+            result = re.sub(self.rules, self.replace, line)
+
+            if not self.in_list_item:
+                self.close_list()
+
+            if self.in_def_list and not line.startswith(' '):
+                self.close_def_list()
+
+            if self.in_table and line[0:2] != '||':
+                self.close_table()
+
+            if len(result) and not self.in_list_item and not self.in_def_list \
+                    and not self.in_table:
+                self.open_paragraph()
+            out.write(result + os.linesep)
+            self.close_table_row()
+
+        self.close_table()
+        self.close_paragraph()
+        self.close_indentation()
+        self.close_list()
+        self.close_def_list()
+
+
+class OneLinerFormatter(Formatter):
+    """
+    A special version of the wiki formatter that only implement a
+    subset of the wiki formatting functions. This version is useful
+    for rendering short wiki-formatted messages on a single line
+    """
+    flavor = 'oneliner'
+
+    def __init__(self, env, absurls=False):
+        Formatter.__init__(self, env, absurls)
+
+    # Override a few formatters to disable some wiki syntax in "oneliner"-mode
+    def _list_formatter(self, match, fullmatch): return match
+    def _indent_formatter(self, match, fullmatch): return match
+    def _heading_formatter(self, match, fullmatch):
+        return wiki.util.escape(match, False)
+    def _definition_formatter(self, match, fullmatch):
+        return wiki.util.escape(match, False)
+    def _table_cell_formatter(self, match, fullmatch): return match
+    def _last_table_cell_formatter(self, match, fullmatch): return match
+
+    def _macro_formatter(self, match, fullmatch):
+        name = fullmatch.group('macroname')
+        if name.lower() == 'br':
+            return ' '
+        elif name == 'comment':
+            return ''
+        else:
+            args = fullmatch.group('macroargs')
+            return '[[%s%s]]' % (name,  args and '(...)' or '')
+
+    def format(self, text, out, shorten=False):
+        if not text:
+            return
+        self.out = out
+        self._open_tags = []
+
+        # Simplify code blocks
+        in_code_block = 0
+        processor = None
+        buf = StringIO()
+        for line in text.strip().splitlines():
+            if line.strip() == '{{{':
+                in_code_block += 1
+            elif line.strip() == '}}}':
+                if in_code_block:
+                    in_code_block -= 1
+                    if in_code_block == 0:
+                        if processor != 'comment':
+                            print>>buf, ' ![...]'
+                        processor = None
+            elif in_code_block:
+                if not processor:
+                    if line.startswith('#!'):
+                        processor = line[2:].strip()
+            else:
+                print>>buf, line
+        result = buf.getvalue()[:-1]
+
+        if shorten:
+            result = wiki.util.shorten_line(result)
+
+        result = re.sub(self.rules, self.replace, result)
+        result = result.replace('[...]', '[…]')
+        if result.endswith('...'):
+            result = result[:-3] + '…'
+
+        # Close all open 'one line'-tags
+        result += self.close_tag(None)
+        out.write(result)
+
+
+def wiki_to_html(wikitext, env, req=None, absurls=False, escape_newlines=False):
+    if not wikitext:
+        return ''
+    out = StringIO()
+    Formatter(env, req, absurls).format(wikitext, out, escape_newlines)
+    return wiki.util.Markup(out.getvalue())
+
+def wiki_to_oneliner(wikitext, env, shorten=False, absurls=False):
+    if not wikitext:
+        return ''
+    out = StringIO()
+    OneLinerFormatter(env, absurls).format(wikitext, out, shorten)
+    return wiki.util.Markup(out.getvalue())
+
diff --git a/wiki/href.py b/wiki/href.py
new file mode 100644
index 0000000..f956f07
--- /dev/null
+++ b/wiki/href.py
@@ -0,0 +1,163 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2004 Jonas Borgstr�m <jonas at edgewall.com>
+# Copyright (C) 2005 Christopher Lenz <cmlenz at gmx.de>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+#         Christopher Lenz <cmlenz at gmx.de>
+
+from urllib import quote, urlencode
+
+
+class Href(object):
+    """
+    Implements a callable that constructs URLs with the given base. The
+    function can be called with any number of positional and keyword
+    arguments which than are used to assemble the URL.
+
+    Positional arguments are appended as individual segments to
+    the path of the URL:
+
+    >>> href = Href('/trac')
+    >>> href('ticket', 540)
+    '/trac/ticket/540'
+    >>> href('ticket', 540, 'attachment', 'bugfix.patch')
+    '/trac/ticket/540/attachment/bugfix.patch'
+    >>> href('ticket', '540/attachment/bugfix.patch')
+    '/trac/ticket/540/attachment/bugfix.patch'
+
+    If a positional parameter evaluates to None, it will be skipped:
+
+    >>> href('ticket', 540, 'attachment')
+    '/trac/ticket/540/attachment'
+
+    The first path segment can also be specified by calling an attribute
+    of the function, as follows:
+
+    >>> href.ticket(540)
+    '/trac/ticket/540'
+    >>> href.changeset(42, format='diff')
+    '/trac/changeset/42?format=diff'
+
+    Simply calling the Href object with no arguments will return the base URL:
+
+    >>> href()
+    '/trac'
+
+    Keyword arguments are added to the query string, unless the value is None:
+
+    >>> href = Href('/trac')
+    >>> href('timeline', format='rss')
+    '/trac/timeline?format=rss'
+    >>> href('timeline', format=None)
+    '/trac/timeline'
+    >>> href('search', q='foo bar')
+    '/trac/search?q=foo+bar'
+
+    Multiple values for one parameter are specified using a sequence (a list or
+    tuple) for the parameter:
+
+    >>> href('timeline', show=['ticket', 'wiki', 'changeset'])
+    '/trac/timeline?show=ticket&show=wiki&show=changeset'
+
+    Alternatively, query string parameters can be added by passing a dict or
+    list as last positional argument:
+
+    >>> href('timeline', {'from': '02/24/05', 'daysback': 30})
+    '/trac/timeline?daysback=30&from=02%2F24%2F05'
+
+    If the order of query string parameters should be preserved, you may also
+    pass a sequence of (name, value) tuples as last positional argument:
+
+    >>> href('query', (('group', 'component'), ('groupdesc', 1)))
+    '/trac/query?group=component&groupdesc=1'
+
+    >>> params = []
+    >>> params.append(('group', 'component'))
+    >>> params.append(('groupdesc', 1))
+    >>> href('query', params)
+    '/trac/query?group=component&groupdesc=1'
+
+    By specifying an absolute base, the function returned will also generate
+    absolute URLs:
+
+    >>> href = Href('http://projects.edgewall.com/trac')
+    >>> href('ticket', 540)
+    'http://projects.edgewall.com/trac/ticket/540'
+
+    >>> href = Href('https://projects.edgewall.com/trac')
+    >>> href('ticket', 540)
+    'https://projects.edgewall.com/trac/ticket/540'
+
+    Finally, the first path segment of the URL to generate can be specified in
+    the following way, mainly to improve readability:
+
+    >>> href = Href('/trac')
+    >>> href.ticket(540)
+    '/trac/ticket/540'
+    >>> href.browser('/trunk/README.txt', format='txt')
+    '/trac/browser/trunk/README.txt?format=txt'
+    """
+
+    def __init__(self, base):
+        self.base = base
+        self._derived = {}
+
+    def __call__(self, *args, **kw):
+        href = self.base
+        if href and href[-1] == '/':
+            href = href[:-1]
+        params = []
+
+        def add_param(name, value):
+            if type(value) in (list, tuple):
+                for i in [i for i in value if i != None]:
+                    params.append((name, i))
+            elif v != None:
+                params.append((name, value))
+
+        if args:
+            lastp = args[-1]
+            if lastp and type(lastp) is dict:
+                for k,v in lastp.items():
+                    add_param(k, v)
+                args = args[:-1]
+            elif lastp and type(lastp) in (list, tuple):
+                for k,v in lastp:
+                    add_param(k, v)
+                args = args[:-1]
+
+        # build the path
+        path = '/'.join([quote(str(arg).strip('/')) for arg in args
+                         if arg != None])
+        if path:
+            href += '/' + path
+
+        # assemble the query string
+        for k,v in kw.items():
+            add_param(k, v)
+
+        if params:
+            href += '?' + urlencode(params)
+
+        return href
+
+    def __getattr__(self, name):
+        if not self._derived.has_key(name):
+            self._derived[name] = lambda *args, **kw: self(name, *args, **kw)
+        return self._derived[name]
+
+
+if __name__ == '__main__':
+    import doctest, sys
+    doctest.testmod(sys.modules[__name__])
diff --git a/wiki/log.py b/wiki/log.py
new file mode 100644
index 0000000..c5a0603
--- /dev/null
+++ b/wiki/log.py
@@ -0,0 +1,70 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2005 Daniel Lundin <daniel at edgewall.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Daniel Lundin <daniel at edgewall.com>
+
+import sys
+
+def logger_factory(logtype='syslog', logfile=None, level='WARNING',
+                   logid='Trac'):
+    try:
+        import logging, logging.handlers
+        logger = logging.getLogger(logid)
+        logtype = logtype.lower()
+        if logtype == 'file':
+            hdlr = logging.FileHandler(logfile)
+        elif logtype in ['winlog', 'eventlog', 'nteventlog']:
+            # Requires win32 extensions
+            hdlr = logging.handlers.NTEventLogHandler(logid,
+                                                      logtype='Application')
+        elif logtype in ['syslog', 'unix']:
+            hdlr = logging.handlers.SysLogHandler('/dev/log')
+        elif logtype in ['stderr']:
+            hdlr = logging.StreamHandler(sys.stderr)
+        else:
+            raise ValueError
+
+        format = 'Trac[%(module)s] %(levelname)s: %(message)s'
+        if logtype == 'file':
+            format = '%(asctime)s ' + format 
+        datefmt = ''
+        level = level.upper()
+        if level in ['DEBUG', 'ALL']:
+            logger.setLevel(logging.DEBUG)
+            datefmt = '%X'
+        elif level == 'INFO':
+            logger.setLevel(logging.INFO)
+        elif level == 'ERROR':
+            logger.setLevel(logging.ERROR)
+        elif level == 'CRITICAL':
+            logger.setLevel(logging.CRITICAL)
+        else:
+            logger.setLevel(logging.WARNING)
+        formatter = logging.Formatter(format,datefmt)
+        hdlr.setFormatter(formatter)
+        logger.addHandler(hdlr) 
+
+    # Logging only supported in Python >= 2.3
+    # Disable logging by using a generic 'black hole' class
+    except (ImportError, ValueError):
+        class DummyLogger:
+            """The world's most fake logger."""
+            def __noop(self, *args, **kwargs):
+                pass
+            debug = info = warning = error = critical = log = exception = __noop
+            warn = fatal = __noop
+            getEffectiveLevel = lambda self: 0
+            isEnabledFor = lambda self, level: 0
+        logger = DummyLogger()
+    return logger
diff --git a/wiki/util.py b/wiki/util.py
new file mode 100644
index 0000000..4f94d29
--- /dev/null
+++ b/wiki/util.py
@@ -0,0 +1,635 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2003-2006 Edgewall Software
+# Copyright (C) 2003-2004 Jonas Borgstr�m <jonas at edgewall.com>
+# Copyright (C) 2006 Matthew Good <trac at matt-good.net>
+# Copyright (C) 2005-2006 Christian Boos <cboos at neuf.fr>
+# All rights reserved.
+#
+# This software is licensed as described in the file Trac_licence.txt, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+#
+# Author: Jonas Borgstr�m <jonas at edgewall.com>
+#         Matthew Good <trac at matt-good.net>
+
+import cgi
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+import os
+import re
+try:
+    frozenset
+except NameError:
+    from sets import ImmutableSet as frozenset
+import sys
+import time
+import tempfile
+
+TRUE =  ['yes', '1', 1, 'true',  'on',  'aye']
+FALSE = ['no',  '0', 0, 'false', 'off', 'nay']
+
+CRLF = '\r\n'
+
+def enum(iterable):
+    """
+    Python 2.2 doesn't have the enumerate() function, so we provide a simple
+    implementation here.
+    """
+    idx = 0
+    for item in iter(iterable):
+        yield idx, item
+        idx += 1
+
+try:
+    reversed = reversed
+except NameError:
+    def reversed(x):
+        if hasattr(x, 'keys'):
+            raise ValueError('mappings do not support reverse iteration')
+        i = len(x)
+        while i > 0:
+            i -= 1
+            yield x[i]
+
+try:
+    sorted = sorted
+except NameError:
+    def sorted(iterable, cmp=None, key=None, reverse=False):
+        """Partial implementation of the "sorted" function from Python 2.4"""
+        lst = [(key(i), i) for i in iterable]
+        lst.sort()
+        if reverse:
+            lst = reversed(lst)
+        return [i for __, i in lst]
+
+class Markup(str):
+    """Marks a string as being safe for inclusion in XML output without needing
+    to be escaped.
+    
+    Strings are normally automatically escaped when added to the HDF.
+    `Markup`-strings are however an exception. Use with care.
+    
+    (since Trac 0.9.3)
+    """
+    def __new__(self, text='', *args):
+        if args:
+            text %= tuple([escape(arg) for arg in args])
+        return str.__new__(self, text)
+
+    def __add__(self, other):
+        return Markup(str(self) + Markup.escape(other))
+
+    def __mul__(self, num):
+        return Markup(str(self) * num)
+
+    def join(self, seq):
+        return Markup(str(self).join([Markup.escape(item) for item in seq]))
+
+    def striptags(self):
+        """Return a copy of the text with all XML/HTML tags removed."""
+        return Markup(re.sub(r'<[^>]*?>', '', self))
+
+    def escape(cls, text, quotes=True):
+        """Create a Markup instance from a string and escape special characters
+        it may contain (<, >, & and ").
+        
+        If the `quotes` parameter is set to `False`, the " character is left as
+        is. Escaping quotes is generally only required for strings that are to
+        be used in attribute values.
+        """
+        if isinstance(text, cls):
+            return text
+        if not text:
+            return cls()
+        text = str(text).replace('&', '&') \
+                        .replace('<', '<') \
+                        .replace('>', '>')
+        if quotes:
+            text = text.replace('"', '"')
+        return cls(text)
+    escape = classmethod(escape)
+
+    def unescape(self):
+        """Reverse-escapes &, <, > and " and returns a `str`."""
+        if not self:
+            return ''
+        return str(self).replace('"', '"') \
+                        .replace('>', '>') \
+                        .replace('<', '<') \
+                        .replace('&', '&')
+
+    def sanitize(self):
+        """Parse the text as HTML and return a cleaned up XHTML representation.
+        
+        This will remove any javascript code or other potentially dangerous
+        elements.
+        
+        If the HTML cannot be parsed, an `HTMLParseError` will be raised by the
+        underlying `HTMLParser` module, which should be handled by the caller of
+        this function.
+        """
+        import htmlentitydefs
+        from HTMLParser import HTMLParser, HTMLParseError
+        from StringIO import StringIO
+
+        buf = StringIO()
+
+        class HTMLSanitizer(HTMLParser):
+            # FIXME: move this out into a top-level class
+            safe_tags = frozenset(['a', 'abbr', 'acronym', 'address', 'area',
+                'b', 'big', 'blockquote', 'br', 'button', 'caption', 'center',
+                'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir',
+                'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1', 'h2',
+                'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins', 'kbd',
+                'label', 'legend', 'li', 'map', 'menu', 'ol', 'optgroup',
+                'option', 'p', 'pre', 'q', 's', 'samp', 'select', 'small',
+                'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody',
+                'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul',
+                'var'])
+            safe_attrs = frozenset(['abbr', 'accept', 'accept-charset',
+                'accesskey', 'action', 'align', 'alt', 'axis', 'border',
+                'cellpadding', 'cellspacing', 'char', 'charoff', 'charset',
+                'checked', 'cite', 'class', 'clear', 'cols', 'colspan', 'color',
+                'compact', 'coords', 'datetime', 'dir', 'disabled', 'enctype',
+                'for', 'frame', 'headers', 'height', 'href', 'hreflang',
+                'hspace', 'id', 'ismap', 'label', 'lang', 'longdesc',
+                'maxlength', 'media', 'method', 'multiple', 'name', 'nohref',
+                'noshade', 'nowrap', 'prompt', 'readonly', 'rel', 'rev', 'rows',
+                'rowspan', 'rules', 'scope', 'selected', 'shape', 'size',
+                'span', 'src', 'start', 'style', 'summary', 'tabindex',
+                'target', 'title', 'type', 'usemap', 'valign', 'value',
+                'vspace', 'width'])
+            uri_attrs = frozenset(['action', 'background', 'dynsrc', 'href',
+                                  'lowsrc', 'src'])
+            safe_schemes = frozenset(['file', 'ftp', 'http', 'https', 'mailto',
+                                      None])
+            empty_tags = frozenset(['br', 'hr', 'img', 'input'])
+            waiting_for = None
+
+            def handle_starttag(self, tag, attrs):
+                if self.waiting_for:
+                    return
+                if tag not in self.safe_tags:
+                    self.waiting_for = tag
+                    return
+                buf.write('<' + tag)
+
+                def _get_scheme(text):
+                    if ':' not in text:
+                        return None
+                    chars = [char for char in text.split(':', 1)[0]
+                             if char.isalnum()]
+                    return ''.join(chars).lower()
+
+                for attrname, attrval in attrs:
+                    if attrname not in self.safe_attrs:
+                        continue
+                    elif attrname in self.uri_attrs:
+                        # Don't allow URI schemes such as "javascript:"
+                        if _get_scheme(attrval) not in self.safe_schemes:
+                            continue
+                    elif attrname == 'style':
+                        # Remove dangerous CSS declarations from inline styles
+                        decls = []
+                        for decl in filter(None, attrval.split(';')):
+                            is_evil = False
+                            if 'expression' in decl:
+                                is_evil = True
+                            for m in re.finditer(r'url\s*\(([^)]+)', decl):
+                                if _get_scheme(m.group(1)) not in self.safe_schemes:
+                                    is_evil = True
+                                    break
+                            if not is_evil:
+                                decls.append(decl.strip())
+                        if not decls:
+                            continue
+                        attrval = '; '.join(decls)
+                    buf.write(' ' + attrname + '="' + escape(attrval) + '"')
+
+                if tag in self.empty_tags:
+                    buf.write(' />')
+                else:
+                    buf.write('>')
+
+            def handle_entityref(self, name):
+                if not self.waiting_for:
+                    if name not in ('amp', 'apos', 'lt', 'gt', 'quot'):
+                        try:
+                            codepoint = htmlentitydefs.name2codepoint[name]
+                            buf.write(unichr(codepoint).encode('utf-8'))
+                        except KeyError:
+                            buf.write('&%s;' % name)
+                    else:
+                        buf.write('&%s;' % name)
+
+            def handle_data(self, data):
+                if not self.waiting_for:
+                    buf.write(escape(data, quotes=False))
+
+            def handle_endtag(self, tag):
+                if self.waiting_for:
+                    if self.waiting_for == tag:
+                        self.waiting_for = None
+                    return
+                if tag not in self.empty_tags:
+                    buf.write('</' + tag + '>')
+
+        # Translate any character or entity references to the corresponding
+        # UTF-8 characters
+        def _ref2utf8(match):
+            ref = match.group(1)
+            if ref.startswith('x'):
+                ref = int(ref[1:], 16)
+            else:
+                ref = int(ref, 10)
+            return unichr(int(ref)).encode('utf-8')
+        text = re.sub(r'&#((?:\d+)|(?:[xX][0-9a-fA-F]+));?', _ref2utf8, self)
+
+        sanitizer = HTMLSanitizer()
+        sanitizer.feed(text)
+        return Markup(buf.getvalue())
+
+
+escape = Markup.escape
+
+def unescape(text):
+    """Reverse-escapes &, <, > and \"."""
+    if not isinstance(text, Markup):
+        return text
+    return text.unescape()
+
+ENTITIES = re.compile(r"&(\w+);")
+def rss_title(text):
+    if isinstance(text, Markup):
+        def replace_entity(match):
+            return match.group(1) in ('amp', 'lt', 'gt', 'apos', 'quot') \
+                   and match.group(0) or ''
+        return Markup(re.sub(ENTITIES, replace_entity,
+                             text.striptags().replace('\n', ' ')))
+    return text
+
+
+
+def to_utf8(text, charset='iso-8859-15'):
+    """Convert a string to UTF-8, assuming the encoding is either UTF-8, ISO
+    Latin-1, or as specified by the optional `charset` parameter."""
+    try:
+        # Do nothing if it's already utf-8
+        u = unicode(text, 'utf-8')
+        return text
+    except UnicodeError:
+        try:
+            # Use the user supplied charset if possible
+            u = unicode(text, charset)
+        except UnicodeError:
+            # This should always work
+            u = unicode(text, 'iso-8859-15')
+        return u.encode('utf-8')
+
+def shorten_line(text, maxlen = 75):
+    if not text:
+        return ''
+    elif len(text) < maxlen:
+        shortline = text
+    else:
+        last_cut = i = j = -1
+        cut = 0
+        while cut < maxlen and cut > last_cut:
+            last_cut = cut
+            i = text.find('[[BR]]', i+1)
+            j = text.find('\n', j+1)
+            cut = max(i,j)
+        if last_cut > 0:
+            shortline = text[:last_cut]+' ...'
+        else:
+            i = text[:maxlen].rfind(' ')
+            if i == -1:
+                i = maxlen
+            shortline = text[:i]+' ...'
+    return shortline
+
+DIGITS = re.compile(r'(\d+)')
+def embedded_numbers(s):
+    """Comparison function for natural order sorting based on
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/214202."""
+    pieces = DIGITS.split(s)
+    pieces[1::2] = map(int, pieces[1::2])
+    return pieces
+
+def hex_entropy(bytes=32):
+    import random
+    return md5(str(random.random() + time.time())).hexdigest()[:bytes]
+
+def pretty_size(size):
+    if size is None:
+        return ''
+
+    jump = 512
+    if size < jump:
+        return '%d bytes' % size
+
+    units = ['kB', 'MB', 'GB', 'TB']
+    i = 0
+    while size >= jump and i < len(units):
+        i += 1
+        size /= 1024.
+
+    return '%.1f %s' % (size, units[i - 1])
+
+def pretty_timedelta(time1, time2=None):
+    """Calculate time delta (inaccurately, only for decorative purposes ;-) for
+    prettyprinting. If time1 is None, the current time is used."""
+    if not time1: time1 = time.time()
+    if not time2: time2 = time.time()
+    if time1 > time2:
+        time2, time1 = time1, time2
+    units = ((3600 * 24 * 365, 'year',   'years'),
+             (3600 * 24 * 30,  'month',  'months'),
+             (3600 * 24 * 7,   'week',   'weeks'),
+             (3600 * 24,       'day',    'days'),
+             (3600,            'hour',   'hours'),
+             (60,              'minute', 'minutes'))
+    age_s = int(time2 - time1)
+    if age_s < 60:
+        return '%i second%s' % (age_s, age_s != 1 and 's' or '')
+    for u, unit, unit_plural in units:
+        r = float(age_s) / float(u)
+        if r >= 0.9:
+            r = int(round(r))
+            return '%d %s' % (r, r == 1 and unit or unit_plural)
+    return ''
+
+def create_unique_file(path):
+    """Create a new file. An index is added if the path exists"""
+    parts = os.path.splitext(path)
+    idx = 1
+    while 1:
+        try:
+            flags = os.O_CREAT + os.O_WRONLY + os.O_EXCL
+            if hasattr(os, 'O_BINARY'):
+                flags += os.O_BINARY
+            return path, os.fdopen(os.open(path, flags), 'w')
+        except OSError:
+            idx += 1
+            # A sanity check
+            if idx > 100:
+                raise Exception('Failed to create unique name: ' + path)
+            path = '%s.%d%s' % (parts[0], idx, parts[1])
+
+def get_reporter_id(req):
+    name = req.session.get('name', None)
+    email = req.session.get('email', None)
+    
+    if req.authname != 'anonymous':
+        return req.authname
+    elif name and email:
+        return '%s <%s>' % (name, email)
+    elif not name and email:
+        return email
+    else:
+        return req.authname
+
+# Date/time utilities
+
+def format_datetime(t=None, format='%x %X', gmt=False):
+    if t is None:
+        t = time.time()
+    if not isinstance(t, (list, tuple, time.struct_time)):
+        if gmt:
+            t = time.gmtime(int(t))
+        else:
+            t = time.localtime(int(t))
+
+    text = time.strftime(format, t)
+    return to_utf8(text)
+
+def format_date(t=None, format='%x', gmt=False):
+    return format_datetime(t, format, gmt)
+
+def format_time(t=None, format='%X', gmt=False):
+    return format_datetime(t, format, gmt)
+
+def get_date_format_hint():
+    t = time.localtime(0)
+    t = (1999, 10, 29, t[3], t[4], t[5], t[6], t[7], t[8])
+    tmpl = time.strftime('%x', t)
+    return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \
+               .replace('10', 'MM', 1).replace('29', 'DD', 1)
+
+def get_datetime_format_hint():
+    t = time.localtime(0)
+    t = (1999, 10, 29, 23, 59, 58, t[6], t[7], t[8])
+    tmpl = time.strftime('%x %X', t)
+    return tmpl.replace('1999', 'YYYY', 1).replace('99', 'YY', 1) \
+               .replace('10', 'MM', 1).replace('29', 'DD', 1) \
+               .replace('23', 'hh', 1).replace('59', 'mm', 1) \
+               .replace('58', 'ss', 1)
+
+def http_date(t=None):
+    """Format t as a rfc822 timestamp"""
+    if t is None:
+        t = time.time()
+    if not isinstance(t, (list, tuple, time.struct_time)):
+        t = time.gmtime(int(t))
+    weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+              'Oct', 'Nov', 'Dec']
+    return '%s, %02d %s %04d %02d:%02d:%02d GMT' % (
+           weekdays[t.tm_wday], t.tm_mday, months[t.tm_mon - 1], t.tm_year,
+           t.tm_hour, t.tm_min, t.tm_sec)
+
+def parse_date(text):
+    seconds = None
+    text = text.strip()
+    for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c',
+                   '%b %d, %Y']:
+        try:
+            date = time.strptime(text, format)
+            seconds = time.mktime(date)
+            break
+        except ValueError:
+            continue
+    if seconds == None:
+        raise ValueError, '%s is not a known date format.' % text
+    return seconds
+
+
+class TracError(Exception):
+    def __init__(self, message, title=None, show_traceback=0):
+        Exception.__init__(self, message)
+        self.message = message
+        self.title = title
+        self.show_traceback = show_traceback
+
+
+class NaivePopen:
+    """This is a deadlock-safe version of popen that returns an object with
+    errorlevel, out (a string) and err (a string).
+    
+    (capturestderr may not work under Windows 9x.)
+
+    Example: print Popen3('grep spam','\n\nhere spam\n\n').out
+    """
+    def __init__(self, command, input=None, capturestderr=None):
+        outfile = tempfile.mktemp()
+        command = '( %s ) > %s' % (command, outfile)
+        if input:
+            infile = tempfile.mktemp()
+            tmp = open(infile, 'w')
+            tmp.write(input)
+            tmp.close()
+            command = command + ' <' + infile
+        if capturestderr:
+            errfile = tempfile.mktemp()
+            command = command + ' 2>' + errfile
+        try:
+            self.err = None
+            self.errorlevel = os.system(command) >> 8
+            outfd = file(outfile, 'r')
+            self.out = outfd.read()
+            outfd.close()
+            if capturestderr:
+                errfd = file(errfile,'r')
+                self.err = errfd.read()
+                errfd.close()
+        finally:
+            if os.path.isfile(outfile):
+                os.remove(outfile)
+            if input and os.path.isfile(infile):
+                os.remove(infile)
+            if capturestderr and os.path.isfile(errfile):
+                os.remove(errfile)
+
+
+def wrap(t, cols=75, initial_indent='', subsequent_indent='',
+         linesep=os.linesep):
+    try:
+        import textwrap
+        t = t.strip().replace('\r\n', '\n').replace('\r', '\n')
+        wrapper = textwrap.TextWrapper(cols, replace_whitespace = 0,
+                                       break_long_words = 0,
+                                       initial_indent = initial_indent,
+                                       subsequent_indent = subsequent_indent)
+        wrappedLines = []
+        for line in t.split('\n'):
+            wrappedLines += wrapper.wrap(line.rstrip()) or ['']
+        return linesep.join(wrappedLines)
+
+    except ImportError:
+        return t
+
+
+def safe__import__(module_name):
+    """
+    Safe imports: rollback after a failed import.
+    
+    Initially inspired from the RollbackImporter in PyUnit,
+    but it's now much simpler and works better for our needs.
+    
+    See http://pyunit.sourceforge.net/notes/reloading.html
+    """
+    already_imported = sys.modules.copy()
+    try:
+        return __import__(module_name, globals(), locals(), [])
+    except Exception, e:
+        for modname in sys.modules.copy():
+            if not already_imported.has_key(modname):
+                del(sys.modules[modname])
+        raise e
+
+
+class Deuglifier(object):
+    def __new__(cls):
+        self = object.__new__(cls)
+        if not hasattr(cls, '_compiled_rules'):
+            cls._compiled_rules = re.compile('(?:' + '|'.join(cls.rules()) + ')')
+        self._compiled_rules = cls._compiled_rules
+        return self
+    
+    def format(self, indata):
+        return re.sub(self._compiled_rules, self.replace, indata)
+
+    def replace(self, fullmatch):
+        for mtype, match in fullmatch.groupdict().items():
+            if match:
+                if mtype == 'font':
+                    return '<span>'
+                elif mtype == 'endfont':
+                    return '</span>'
+                return '<span class="code-%s">' % mtype
+
+# Original license for md5crypt:
+# Based on FreeBSD src/lib/libcrypt/crypt.c 1.2
+#
+# "THE BEER-WARE LICENSE" (Revision 42):
+# <phk at login.dknet.dk> wrote this file.  As long as you retain this notice you
+# can do whatever you want with this stuff. If we meet some day, and you think
+# this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+
+def md5crypt(password, salt, magic='$1$'):
+    # /* The password first, since that is what is most unknown */
+    # /* Then our magic string */
+    # /* Then the raw salt */
+    m = md5()
+    m.update(password + magic + salt)
+
+    # /* Then just as many characters of the MD5(pw,salt,pw) */
+    mixin = md5(password + salt + password).digest()
+    for i in range(0, len(password)):
+        m.update(mixin[i % 16])
+
+    # /* Then something really weird... */
+    # Also really broken, as far as I can tell.  -m
+    i = len(password)
+    while i:
+        if i & 1:
+            m.update('\x00')
+        else:
+            m.update(password[0])
+        i >>= 1
+
+    final = m.digest()
+
+    # /* and now, just to make sure things don't run too fast */
+    for i in range(1000):
+        m2 = md5()
+        if i & 1:
+            m2.update(password)
+        else:
+            m2.update(final)
+
+        if i % 3:
+            m2.update(salt)
+
+        if i % 7:
+            m2.update(password)
+
+        if i & 1:
+            m2.update(final)
+        else:
+            m2.update(password)
+
+        final = m2.digest()
+
+    # This is the bit that uses to64() in the original code.
+
+    itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+
+    rearranged = ''
+    for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)):
+        v = ord(final[a]) << 16 | ord(final[b]) << 8 | ord(final[c])
+        for i in range(4):
+            rearranged += itoa64[v & 0x3f]; v >>= 6
+
+    v = ord(final[11])
+    for i in range(2):
+        rearranged += itoa64[v & 0x3f]; v >>= 6
+
+    return magic + salt + '$' + rearranged

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/netepi-collection.git



More information about the debian-med-commit mailing list